Repository navigation
Expand file tree
/
Copy pathdocker-compose.prod.yml
More file actions
76 lines (73 loc) · 2.98 KB
/
Copy pathdocker-compose.prod.yml
File metadata and controls
76 lines (73 loc) · 2.98 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
# Pack — backend-only production stack for one ECS box (cheapest hackathon-demo shape).
#
# Only the engine + gateway + web/nginx run here. Postgres and Redis are EXTERNAL free-tier services
# (Neon for Postgres, Upstash for Redis — see deploy/ECS_DEPLOY_GUIDE.md) reached over the public
# internet via POSTGRES_URL/REDIS_URL in .env.prod, both requiring TLS. This keeps the ECS box small
# (no DB containers competing for its RAM) and the whole stack's cost to just the ECS instance itself.
#
# cd deploy
# cp .env.prod.example .env.prod # fill in QWEN_API_KEY, POSTGRES_URL, REDIS_URL, SESSION_SECRET, API_AUTH_TOKEN
# htpasswd -bc .htpasswd <user> <password>
# docker compose -f docker-compose.prod.yml up -d --build
#
# Build contexts are the repo root (..) so gateway/ and backend/ are used as-is, untouched.
#
# Frontend is deployed SEPARATELY (Vercel/Netlify/Cloudflare Pages — see the deploy guide) pointing
# VITE_ENGINE_URL/VITE_GATEWAY_URL at this box's public IP. The `web` service below is still here and
# still works (nginx + Basic auth + the same-origin /api and /ws proxy) if you'd rather serve the
# frontend from this box too — just don't run both; pick one frontend path.
services:
engine:
build:
context: ..
dockerfile: deploy/engine.Dockerfile
env_file: [.env.prod]
expose: ["8000"]
restart: unless-stopped
# Liveness is defined in the Dockerfile HEALTHCHECK (hits /health).
deploy:
resources:
limits: { cpus: "2.0", memory: 2g }
gateway:
build:
context: ..
dockerfile: deploy/gateway.Dockerfile
env_file: [.env.prod] # reads REDIS_URL; ignores the rest
environment:
GATEWAY_PORT: "8080"
expose: ["8080"]
restart: unless-stopped
deploy:
resources:
limits: { cpus: "0.5", memory: 256m }
web:
build:
context: ..
dockerfile: deploy/web.Dockerfile
args:
VITE_ENGINE_URL: /api
# Must match what the app reads (frontend/src/env.ts → VITE_GATEWAY_URL); a mismatched name
# silently falls back to ws://localhost:8080 and the live stream never connects in prod.
VITE_GATEWAY_URL: /ws
# nginx-unprivileged listens on 8080 inside the container; publish it as host :80.
ports:
- "80:8080"
# - "443:8443" # enable after you add TLS (see DEPLOY.md)
# The Basic-auth password file (create it before `up`: htpasswd -bc deploy/.htpasswd <user> <pass>).
# Read-only, and gitignored — never committed.
volumes:
- ./.htpasswd:/etc/nginx/.htpasswd:ro
healthcheck:
# busybox wget (bundled in the nginx alpine image) against the unauthenticated /healthz.
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
depends_on:
engine: { condition: service_healthy }
gateway: { condition: service_healthy }
restart: unless-stopped
deploy:
resources:
limits: { cpus: "0.5", memory: 128m }