-
Notifications
You must be signed in to change notification settings - Fork 54
Expand file tree
/
Copy pathdocker-compose.prod.yml
More file actions
118 lines (109 loc) · 5.58 KB
/
Copy pathdocker-compose.prod.yml
File metadata and controls
118 lines (109 loc) · 5.58 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
# Production overlay — adds TLS (Caddy auto-HTTPS), enforces auth, and sets restart policies.
# Layer it on top of the base compose:
#
# DOMAIN=app.example.com docker compose -f docker-compose.yml -f docker-compose.prod.yml \
# --profile full up -d --build
#
# Requires: a VM with Docker, a DNS A record for $DOMAIN pointing at it, and ports 80+443 open.
# Lock the rest down with a firewall (e.g. `ufw allow 80,443/tcp` and deny 8000/8080/5432/9000).
# Fail-closed datastore credentials, shared by every service that opens the database or the object
# store. This is an anchor rather than two copies because the API and the job worker MUST fail closed
# in the same way: the worker inherits the base compose's permissive `${POSTGRES_PASSWORD:-bim}`
# unless something overrides it here, so a copy that drifted would leave the worker connecting with
# the dev default password in production while the API refused to start — and only the API's refusal
# would be visible.
x-prod-datastore: &prod-datastore
DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-bim}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env for production}@postgres:5432/${POSTGRES_DB:-bim}"
S3_SECRET_KEY: "${S3_SECRET_KEY:?set S3_SECRET_KEY in .env for production}"
services:
caddy:
image: caddy:2
restart: unless-stopped
ports: ["80:80", "443:443"]
environment:
DOMAIN: "${DOMAIN:?set DOMAIN to your public hostname}"
volumes:
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on: [web]
profiles: ["full"]
web:
restart: unless-stopped
# Shared store for per-IP rate limiting + login-lockout counters across the API's uvicorn workers
# (the base stack runs UVICORN_WORKERS=4; without Redis those counters are per-process).
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--save", "", "--appendonly", "no"] # ephemeral counters; no persistence
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
profiles: ["full"]
api:
restart: unless-stopped
depends_on:
redis: { condition: service_healthy }
environment:
AEC_RBAC: "1" # enforce auth/roles in production (override the dev default)
AEC_REQUIRE_SECRET: "1" # refuse to start without AEC_AUTH_SECRET (fail-closed)
AEC_HSTS: "1" # served over HTTPS via Caddy → enable HSTS
AEC_COOKIE_SECURE: "1" # force the Secure flag on the auth cookie
AEC_CSP: "${AEC_CSP:-1}" # strict resource Content-Security-Policy
AEC_SIGNED_URL_TTL: "${AEC_SIGNED_URL_TTL:-3600}"
AEC_MAX_UPLOAD_MB: "${AEC_MAX_UPLOAD_MB:-1024}"
AEC_RATE_LIMIT_RPM: "${AEC_RATE_LIMIT_RPM:-600}" # per-IP throttle (shared via Redis below)
AEC_REDIS_URL: "redis://redis:6379/0" # shared rate-limit + login-lockout across workers
AEC_ADMIN_EMAILS: "${AEC_ADMIN_EMAILS:-}" # platform admins (Settings/audit/user mgmt)
# Host-header pinning (opt-in): e.g. "app.example.com,localhost" — include localhost so the
# container healthcheck and the internal nginx proxy keep working. Empty = no restriction.
AEC_ALLOWED_HOSTS: "${AEC_ALLOWED_HOSTS:-}"
# Fail-closed on the datastore secrets in production — refuse to start rather than silently fall
# back to the dev defaults (the base compose uses `${…:-bim}` / `${…:-minioadmin}` for local dev).
<<: *prod-datastore
# Resource limits (opt-in): same env vars as the base compose (see .env.example). Kept OFF by
# default so a too-low cap can't OOM-kill a legitimate large-IFC reconvert. Uncomment + size
# for the production host.
# deploy:
# resources:
# limits:
# memory: "${AEC_API_MEM_LIMIT:-4g}"
# cpus: "${AEC_API_CPUS:-2}"
# JOB-WORKER-SPLIT — the queue container from the base compose, made production-shaped. The base
# file already sets `AEC_JOB_WORKER=off` on the API and defines this service; all that is added here
# is the restart policy and the fail-closed credentials.
#
# This is the service to scale when conversions back up — `--scale worker=N` — and the one to give
# the memory limit to, because after the split it is where the large IFC parses actually happen.
worker:
restart: unless-stopped
healthcheck: { disable: true } # inherited from the API image; this service binds no port
environment:
<<: *prod-datastore
AEC_JOB_WORKER: "inline" # this process IS the worker
# deploy: # opt-in cap — the heavy parses live here now, so size this one
# resources: # before the API's.
# limits:
# memory: "${AEC_WORKER_MEM_LIMIT:-4g}"
# cpus: "${AEC_WORKER_CPUS:-2}"
postgres:
restart: unless-stopped
environment:
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env for production}"
# deploy: # opt-in cap — uncomment + size for your host
# resources:
# limits:
# memory: "${AEC_PG_MEM_LIMIT:-2g}"
minio:
restart: unless-stopped
environment:
MINIO_ROOT_PASSWORD: "${S3_SECRET_KEY:?set S3_SECRET_KEY in .env for production}"
# deploy: # opt-in cap — uncomment + size for your host
# resources:
# limits:
# memory: "${AEC_MINIO_MEM_LIMIT:-1g}"
volumes:
caddy-data:
caddy-config: