-
-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathcompose.yaml
More file actions
157 lines (155 loc) · 6.07 KB
/
Copy pathcompose.yaml
File metadata and controls
157 lines (155 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/
# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
# External Caddy config needed for bookhive.social:
# bookhive.social, *.bookhive.social {
# reverse_proxy pds:3000
# }
services:
pds:
image: ghcr.io/bluesky-social/pds
restart: unless-stopped
environment:
PDS_HOSTNAME: "bookhive.social"
PDS_ADMIN_PASSWORD: "CHANGE_ME_admin_password"
PDS_JWT_SECRET: "CHANGE_ME_jwt_secret"
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: "CHANGE_ME_rotation_key"
PDS_INVITE_REQUIRED: "true"
PDS_DATA_DIRECTORY: "/pds"
PDS_BLOBSTORE_DISK_LOCATION: "/pds/blocks"
PDS_EMAIL_SMTP_URL: "smtps://resend:re_CHANGE_ME@smtp.resend.com:465/"
PDS_EMAIL_FROM_ADDRESS: "noreply@bookhive.social"
PDS_RATE_LIMITS_ENABLED: "true"
volumes:
- pds_data:/pds
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/xrpc/_health"]
interval: 10s
timeout: 5s
retries: 5
openobserve:
image: public.ecr.aws/zinclabs/openobserve:latest
restart: unless-stopped
environment:
ZO_ROOT_USER_EMAIL: "user@bookhive.buzz"
ZO_ROOT_USER_PASSWORD: "password"
ports:
- "5080:5080"
volumes:
- telem_data:/data
# No healthcheck, and deliberately not a `depends_on` for the server.
# Two reasons, both checked rather than assumed:
# 1. The image is distroless — no /bin/sh, no curl, no wget (verified on
# the host), so any `test:` here would report permanently unhealthy and
# make `docker ps` lie in the opposite direction.
# 2. The app must not refuse to boot because its telemetry backend is
# down. The log transport already tolerates connection-refused and the
# OTLP exporter retries; gating startup on OpenObserve would turn
# observability into a SPOF for the product.
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
restart: unless-stopped
volumes:
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
depends_on:
- openobserve
- server
imgproxy:
image: ghcr.io/imgproxy/imgproxy:latest
restart: unless-stopped
# Not exposed publicly — only reachable by the server service on the
# internal compose network. The server signs URLs and reverse-proxies to it.
environment:
IMGPROXY_KEY: "CHANGE_ME_imgproxy_key_hex"
IMGPROXY_SALT: "CHANGE_ME_imgproxy_salt_hex"
# Lock imgproxy to the same source hosts the server allowlists.
IMGPROXY_ALLOWED_SOURCES: "https://i.gr-assets.com/,https://cdn.bsky.app/,https://images.isbndb.com/"
IMGPROXY_MAX_SRC_RESOLUTION: "50"
healthcheck:
test: ["CMD", "imgproxy", "health"]
interval: 10s
timeout: 5s
retries: 5
server:
restart: unless-stopped
# PID 1 is the cluster supervisor, which does not reap orphans. Without an
# init shim every HEALTHCHECK `wget` leaks a zombie (484 observed in ~4h on
# 2026-08-01), ending in PID exhaustion. The image also ships tini, so this
# is belt-and-braces for stacks that don't use this compose file.
init: true
build:
context: .
depends_on:
pds:
condition: service_healthy
imgproxy:
condition: service_healthy
environment:
NODE_ENV: "production"
# Worker processes sharing port 8080 via SO_REUSEPORT (server/cluster.ts).
# One fewer than cpus so worker-0's migration/ingester startup has headroom.
WEB_CONCURRENCY: "3"
COOKIE_SECRET: "secret_password_do_not_use_this_please"
DB_PATH: "/data/db.sqlite"
KV_DB_PATH: "/data/kv.sqlite"
PUBLIC_URL: "https://bookhive.buzz"
PDS_URL: "http://pds:3000"
PDS_ADMIN_PASSWORD: "CHANGE_ME_admin_password"
OPEN_OBSERVE_USER: "user@bookhive.buzz"
OPEN_OBSERVE_PASSWORD: "password"
# Must be container-reachable: inside this container `localhost` is the
# app itself, not OpenObserve. Both the logs transport and the OTLP trace
# exporter derive their endpoints from this, so a wrong value silently
# disables observability rather than failing loudly.
OPEN_OBSERVE_URL: "http://openobserve:5080"
IMGPROXY_URL: "http://imgproxy:8080"
IMGPROXY_KEY: "CHANGE_ME_imgproxy_key_hex"
IMGPROXY_SALT: "CHANGE_ME_imgproxy_salt_hex"
ports:
- 8080:8080
volumes:
- data:/data
# Leave the other ~4 host CPUs for pds/imgproxy/openobserve and future services.
cpus: "4"
# ~300-400MB anonymous per worker. The old budget assumed a ~1GB SQLite
# mmap on top; DB_MMAP_SIZE now defaults to 0, so that term is gone.
mem_limit: 3g
# The commented out section below is an example of how to define a PostgreSQL
# database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the
# database data between container restarts. The `db-password` secret is used
# to set the database password. You must create `db/password.txt` and add
# a password of your choosing to it before running `docker-compose up`.
# depends_on:
# db:
# condition: service_healthy
# db:
# image: postgres
# restart: always
# user: postgres
# secrets:
# - db-password
# volumes:
# - db-data:/var/lib/postgresql/data
# environment:
# - POSTGRES_DB=example
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
# expose:
# - 5432
# healthcheck:
# test: [ "CMD", "pg_isready" ]
# interval: 10s
# timeout: 5s
# retries: 5
volumes:
data:
telem_data:
pds_data:
# secrets:
# db-password:
# file: db/password.txt