-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathdocker-compose.prod.yml
More file actions
168 lines (157 loc) · 6.89 KB
/
Copy pathdocker-compose.prod.yml
File metadata and controls
168 lines (157 loc) · 6.89 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
# Production stack for codever.dev (design doc: docker-compose-prod.md in the private
# codever-migrate-to-docker repo). Secrets come from a server-side `.env` file (never
# committed). `${VAR:?}` guards hard-fail on missing secrets — intentional.
name: codever
networks:
backend:
volumes:
postgres_data:
services:
nginx:
image: nginx:stable
container_name: codever-nginx
restart: unless-stopped
networks:
backend:
aliases:
# Inside the Docker network, www.codever.dev resolves to THIS nginx.
# Required: the API's env.json auth-server-url MUST be the public
# https://www.codever.dev/auth (keycloak-connect validates the token
# issuer against it, and KC_HOSTNAME makes Keycloak issue that iss).
# The alias makes that URL reachable from codever-api even BEFORE the
# DNS cutover (and keeps auth traffic on-host after it).
- www.codever.dev
- codever.dev
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro # codever.dev server blocks
- /data/codever/ui-dist/browser:/usr/share/nginx/html:ro # Angular 19 browser build
- /etc/letsencrypt:/etc/letsencrypt:ro # certs (shared with certbot)
- /data/codever/certbot-webroot:/var/www/certbot:ro # ACME http-01 webroot
healthcheck:
# no -f: until the UI is deployed (Part 6) the root returns 403 — any HTTP
# response over TLS proves nginx + cert are working
test: ["CMD", "curl", "-ks", "-o", "/dev/null", "https://localhost/"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
- codever-api
- keycloak
certbot:
image: certbot/certbot
container_name: codever-certbot
restart: unless-stopped
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /data/codever/certbot-webroot:/var/www/certbot
# check twice a day; renew when <30 days remain (certbot default)
entrypoint: >
/bin/sh -c 'trap exit TERM;
while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done'
codever-api:
build: ./apps/codever-api # or image: ghcr.io/codeverdotdev/codever-api:<tag> when using CI
container_name: codever-api
restart: unless-stopped
networks: [backend]
env_file: .env
environment:
NODE_ENV: production
MONGODB_HOST: mongo # Docker service name, not localhost
MONGODB_PORT: "27017"
volumes:
# env.json is git-ignored + excluded via .dockerignore (server-side secret, never
# baked into the image) → must be mounted. Target MUST match the Dockerfile WORKDIR
# (/opt/node_app/app), which is where config.js resolves require('../../env.json').
- ./apps/codever-api/env.json:/opt/node_app/app/env.json:ro
logging:
driver: json-file
options: { max-size: "50m", max-file: "5" }
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/version', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
mongo:
condition: service_healthy
keycloak:
image: quay.io/keycloak/keycloak:24.0
container_name: codever-keycloak
restart: unless-stopped
networks: [backend]
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${KC_DB_PASSWORD:?set in .env}
# BARE hostname only — NOT the full URL: KC 24 prepends https:// itself and
# appends KC_HTTP_RELATIVE_PATH; a full URL here produced the mangled issuer
# "https://https//www.codever.dev/auth/auth/realms/bookmarks" (found 2026-07-31)
KC_HOSTNAME: www.codever.dev
# pin BACKCHANNEL urls (token/userinfo/jwks) to the hostname too — without this
# they are derived from the request's Host header (e.g. "localhost" in curl checks);
# harmless for our adapters (they don't use discovery) but deterministic is better:
KC_HOSTNAME_STRICT_BACKCHANNEL: "true"
KC_HTTP_RELATIVE_PATH: /auth # CRITICAL: keeps keycloak-js 12 + nginx /auth/ working
KC_HTTP_ENABLED: "true" # plain HTTP inside the Docker network
KC_PROXY_HEADERS: xforwarded # trust X-Forwarded-* from nginx
# KC 24 names! (KC_BOOTSTRAP_ADMIN_* is Keycloak 26+ — KC 24 silently ignores
# them → "Local access required" and no admin user; found 2026-08-01).
# Creates the master-realm admin at startup if it doesn't exist yet.
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:?set in .env}
KC_HEALTH_ENABLED: "true" # serves /auth/health/* on 8080 (used by healthcheck)
# --import-realm is safe to keep permanently: existing realms are SKIPPED on
# startup ("Realm 'bookmarks' already exists. Import skipped") — it only
# imports into an empty DB (first boot, or after the Postgres wipe in the
# re-import procedure, runbook 4.4). No need to toggle it off afterwards.
command: start --import-realm
volumes:
- ./docker-compose-setup/keycloak-export-import:/opt/keycloak/data/import:ro
- ./apps/codever-keycloak-theme/codever:/opt/keycloak/themes/codever:ro
healthcheck:
# KC image has no curl/wget — use bash's /dev/tcp against the health endpoint
# (enabled via KC_HEALTH_ENABLED; served under the /auth relative path on 8080 in KC 24)
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && printf 'GET /auth/health/ready HTTP/1.0\\r\\n\\r\\n' >&3 && head -1 <&3 | grep -q 200"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s # JVM + import can take a while on first boot
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
container_name: codever-postgres
restart: unless-stopped
networks: [backend]
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${KC_DB_PASSWORD:?set in .env}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 5s
retries: 5
mongo:
image: mongo:5.0 # ceiling for mongoose ^5.13 — do NOT bump without code phase
container_name: codever-mongo
restart: unless-stopped
networks: [backend]
environment:
MONGO_INITDB_ROOT_USERNAME: mongoadmin
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_PASSWORD:?set in .env}
volumes:
- /data/codever/mongodb:/data/db # bind mount → obvious backup location
command: ["mongod", "--auth", "--bind_ip_all"]
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5