-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose.yml
More file actions
194 lines (188 loc) · 8.03 KB
/
Copy pathcompose.yml
File metadata and controls
194 lines (188 loc) · 8.03 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# A complete board on one machine: Postgres, the migration, the web server
# and the worker.
#
# `docker compose up -d --build` should produce a working board with no other
# setup, which is the parity claim against MyBB's "upload and run" story. This
# is the deployment route this project supports, in its by-hand shape;
# docs/self-hosting.md is the walkthrough, docs/quickstart.md is the guided
# one, and everything below is meant to be read as well as run.
#
# Four containers, and each earns its place: Postgres holds the board, `migrate`
# runs to completion before anything serves so the schema is never behind the
# code, `web` answers requests, and `worker` runs the tick. The worker is not
# optional — every catch-up operation on this board runs on that loop, and when
# it stops *nothing fails*, it just quietly stops happening.
#
# Nothing here terminates TLS. Put a reverse proxy in front (Caddy, nginx,
# Traefik) and bind the web port to localhost so the proxy is the only way in:
#
# PORT=127.0.0.1:3000
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: community
# Generate this with `openssl rand -hex 32`, not base64. It is substituted
# into the postgres:// URL below, and base64's alphabet includes `/` and
# `+` — which makes the connection string unparseable and the migration
# fail with `TypeError: Invalid URL`, naming nothing that would send you
# here.
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-community}
POSTGRES_DB: community
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U community -d community']
interval: 10s
timeout: 5s
retries: 5
migrate:
build:
# `..` because this file is run from inside docker/ (the guide's
# `cd meith/docker`), and compose resolves relative paths against the
# project directory — which defaults to this file's folder. The two
# Coolify build files say `.` instead, because Coolify overrides the
# project directory to the repository root. Do not "fix" either to
# match the other.
context: ..
dockerfile: docker/Dockerfile
# Runs to completion then exits; `web` waits for it so the schema is always
# applied before the first request rather than racing it.
environment:
# A role, not a command: drizzle-kit is a development tool and is not in
# the pruned standalone node_modules, so `node node_modules/.bin/drizzle-kit`
# never worked here. `COMMUNITY_ROLE=migrate` runs the same runMigrations()
# the operator CLI does.
COMMUNITY_ROLE: migrate
DATABASE_URL: postgres://community:${POSTGRES_PASSWORD:-community}@postgres:5432/community
DATA_SOURCE: postgres
# The migration signs nothing, and still needs these.
#
# `env` is one environment contract for every process the board runs, with
# no per-role exemptions — so the production rules apply to the migrator
# too, and it exits naming both secrets before it opens a connection. That
# is the trade the single schema buys: no role can quietly boot with less
# than the others. Passing them here costs one line; the alternative is a
# carve-out that would let the *web* server start without them one day.
AUTH_SECRET: ${AUTH_SECRET:?AUTH_SECRET must be set}
TICK_SECRET: ${TICK_SECRET:?TICK_SECRET must be set}
depends_on:
postgres:
condition: service_healthy
restart: 'no'
web:
build:
context: ..
dockerfile: docker/Dockerfile
restart: unless-stopped
ports:
- '${PORT:-3000}:3000'
environment:
DATABASE_URL: postgres://community:${POSTGRES_PASSWORD:-community}@postgres:5432/community
DATA_SOURCE: postgres
# Required. Generate with: openssl rand -base64 32
AUTH_SECRET: ${AUTH_SECRET:?AUTH_SECRET must be set}
# Guards the tick endpoint, which is publicly routable.
TICK_SECRET: ${TICK_SECRET:?TICK_SECRET must be set}
QUEUE_DRIVER: postgres
CACHE_DRIVER: next
FILESTORE_DRIVER: local
APP_URL: ${APP_URL:-http://localhost:3000}
# Mail, forwarded rather than hard-coded so it can be configured in `.env`
# without editing this file. An empty value here is treated as unset; see
# withoutEmptyValues in packages/core/src/env.ts.
#
# Leaving MAIL_DRIVER at `log` no longer means "no mail": it means the
# board decides, from the installer on first run or from
# /admin/settings?group=mail afterwards, with no redeploy. Setting it to
# `http` or `smtp` makes this file authoritative and the settings screen
# read-only — the right choice when the credential must not live in the
# database.
MAIL_DRIVER: ${MAIL_DRIVER:-log}
MAIL_FROM: ${MAIL_FROM:-}
MAIL_HTTP_ENDPOINT: ${MAIL_HTTP_ENDPOINT:-}
MAIL_HTTP_TOKEN: ${MAIL_HTTP_TOKEN:-}
MAIL_SMTP_HOST: ${MAIL_SMTP_HOST:-}
MAIL_SMTP_PORT: ${MAIL_SMTP_PORT:-}
MAIL_SMTP_SECURITY: ${MAIL_SMTP_SECURITY:-}
MAIL_SMTP_USERNAME: ${MAIL_SMTP_USERNAME:-}
MAIL_SMTP_PASSWORD: ${MAIL_SMTP_PASSWORD:-}
volumes:
- uploads:/app/.uploads
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
# The worker: the same image, with COMMUNITY_ROLE=worker, running the scheduler
# in-process on its own one-minute loop.
#
# It replaces the `tick` sidecar below rather than joining it — running both
# is harmless (the task claim is what makes concurrent ticks safe) but it is
# two things doing one job. The sidecar is kept, commented, because a board
# that would rather not give a second container database credentials can use
# it instead; that is the trade, and it is the operator's to make.
worker:
build:
context: ..
dockerfile: docker/Dockerfile
restart: unless-stopped
environment:
COMMUNITY_ROLE: worker
DATABASE_URL: postgres://community:${POSTGRES_PASSWORD:-community}@postgres:5432/community
DATA_SOURCE: postgres
AUTH_SECRET: ${AUTH_SECRET:?AUTH_SECRET must be set}
TICK_SECRET: ${TICK_SECRET:?TICK_SECRET must be set}
QUEUE_DRIVER: postgres
CACHE_DRIVER: memory
FILESTORE_DRIVER: local
APP_URL: ${APP_URL:-http://localhost:3000}
# The worker is what actually sends queued mail, so it needs the same
# settings as the web server rather than a subset.
MAIL_DRIVER: ${MAIL_DRIVER:-log}
MAIL_FROM: ${MAIL_FROM:-}
MAIL_HTTP_ENDPOINT: ${MAIL_HTTP_ENDPOINT:-}
MAIL_HTTP_TOKEN: ${MAIL_HTTP_TOKEN:-}
MAIL_SMTP_HOST: ${MAIL_SMTP_HOST:-}
MAIL_SMTP_PORT: ${MAIL_SMTP_PORT:-}
MAIL_SMTP_SECURITY: ${MAIL_SMTP_SECURITY:-}
MAIL_SMTP_USERNAME: ${MAIL_SMTP_USERNAME:-}
MAIL_SMTP_PASSWORD: ${MAIL_SMTP_PASSWORD:-}
volumes:
- uploads:/app/.uploads
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
# The pre-worker approach, kept for the operator who prefers it: a tiny
# sidecar curling the tick endpoint once a minute, so the only container
# holding database credentials is the web server. That is the whole of the
# trade, and it is the operator's to make. Enable this *or* `worker`, never
# both — harmless if you do (a task claims its work in the database, so
# concurrent ticks are safe) but it is two things doing one job.
#
# docker compose --profile curl-tick up -d
tick:
profiles: ['curl-tick']
image: alpine:3.20
restart: unless-stopped
environment:
TICK_SECRET: ${TICK_SECRET:?TICK_SECRET must be set}
command:
- sh
- -c
- |
apk add --no-cache curl >/dev/null
while true; do
curl -fsS -m 55 -H "Authorization: Bearer $$TICK_SECRET" \
http://web:3000/api/system/tick >/dev/null 2>&1 \
|| echo "tick failed at $$(date -Is)"
sleep 60
done
depends_on:
- web
volumes:
pgdata:
uploads: