-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
151 lines (141 loc) · 5.26 KB
/
Copy pathdocker-compose.yml
File metadata and controls
151 lines (141 loc) · 5.26 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
# Production single-box topology. `docker compose up -d --build` brings up the full stack behind
# Caddy (auto-HTTPS). Postgres and MinIO stay on the internal network only (no host ports); the
# public surface is Caddy on 80/443. For local dev, opt into docker-compose.dev.yml explicitly
# (`-f docker-compose.yml -f docker-compose.dev.yml`); it is deliberately NOT named
# docker-compose.override.yml so its host-port/no-TLS settings never auto-merge here. See
# docs/deploy.md.
# Shared build config: pass NEXT_PUBLIC_WS_URL as a build arg so Next inlines it into the client
# bundle at build time (F29). A runtime-only env_file does NOT reach the client bundle.
x-app-build: &app-build
context: .
args:
NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-}
# Optional version stamp for the update-check banner. Unset falls back to package.json, then "dev".
APP_VERSION: ${APP_VERSION:-}
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
# Substituted into the Caddyfile as {$APP_DOMAIN} / {$ACME_EMAIL}. Compose auto-loads
# these from the project .env for interpolation.
APP_DOMAIN: ${APP_DOMAIN}
ACME_EMAIL: ${ACME_EMAIL}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddydata:/data
- caddyconfig:/config
depends_on:
app:
condition: service_healthy
ws:
condition: service_started
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-warpdrive}
# Overridable, with the historical value as the fallback so existing boxes keep booting.
# Postgres only applies this at initdb, so setting it against an EXISTING pgdata volume
# does NOT rotate the password; it just makes DATABASE_URL wrong. Choose it on a fresh
# deploy, and keep it identical to the password inside DATABASE_URL.
# Not reachable from outside the box today (this service publishes no host port and Caddy
# is the only public listener), so the weak default is a defense-in-depth gap rather than a
# live hole. It becomes a live hole the moment anyone adds a `ports:` mapping here.
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-warpdrive}
POSTGRES_DB: ${POSTGRES_DB:-warpdrive}
volumes: [pgdata:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-warpdrive}"]
interval: 5s
timeout: 5s
retries: 10
minio:
image: minio/minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
# Browser uploads/downloads hit MinIO cross-origin (app on APP_DOMAIN, storage on
# s3.APP_DOMAIN), so the S3 API must return CORS headers or the upload fetch rejects.
MINIO_API_CORS_ALLOW_ORIGIN: ${BASE_URL}
# Caddy reaches MinIO over the internal network (see Caddyfile s3.<domain> vhost); no host
# port publish needed. MINIO_ENDPOINT must be the public https://s3.<domain> URL.
volumes: [miniodata:/data]
# One-shot: create the app bucket idempotently and keep it private. The app itself never
# creates the bucket, so on a fresh box uploads would fail without this step.
createbuckets:
image: minio/mc
depends_on:
minio:
condition: service_started
env_file: .env
# $$VAR (not $VAR) so Compose passes the literal to the shell, which expands it from the
# container env (env_file), not from the host at parse time.
entrypoint: >
/bin/sh -c "
until mc alias set local http://minio:9000 $$MINIO_ACCESS_KEY $$MINIO_SECRET_KEY; do
echo 'waiting for minio'; sleep 2;
done;
mc mb --ignore-existing local/$$MINIO_BUCKET;
mc anonymous set none local/$$MINIO_BUCKET;
"
# One-shot: apply forward-only Drizzle migrations, then exit. Gates the long-running tier.
migrate:
build: *app-build
command: ["node", "dist/migrate.mjs"]
env_file: .env
depends_on:
postgres:
condition: service_healthy
app:
build: *app-build
restart: unless-stopped
command: ["node_modules/.bin/next", "start"]
env_file: .env
healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost:3000/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
depends_on:
migrate:
condition: service_completed_successfully
createbuckets:
condition: service_completed_successfully
ws:
build: *app-build
restart: unless-stopped
command: ["node", "dist/ws.mjs"]
env_file: .env
healthcheck:
test:
- "CMD"
- "node"
- "-e"
- "require('node:net').connect(8080,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))"
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
depends_on:
migrate:
condition: service_completed_successfully
worker:
build: *app-build
restart: unless-stopped
command: ["node", "dist/worker.mjs"]
env_file: .env
depends_on:
migrate:
condition: service_completed_successfully
volumes:
pgdata:
miniodata:
caddydata:
caddyconfig: