8.0 KiB
Stack trace for docker-compose.prod.yml deployment
Use this doc to see how the frontend, backend, Postgres, and MinIO connect, in what order Compose starts services, and how to verify each tier after deploy. For Postgres volume errors, HTTP vs HTTPS on Vite, and .env dotfiles, see deploy-production-docker.md.
1. High-level dependency graph
Compose service names (postgres, minio, etc.) resolve on the profyt-net bridge (10.5.0.0/16). Static IPs are defined in docker-compose.prod.yml for readability; traffic still uses DNS names be0, minio, postgres.
flowchart TB
subgraph browser [Browser on the Internet]
U[User]
end
subgraph host [Docker host ports]
FEPORT["HOST:FE_PORT ~ Vite HTTP"]
MINAPI["HOST:MINIO_API_PORT"]
MINUI["HOST:MINIO_CONSOLE_PORT"]
end
subgraph net [Compose network profyt-net]
fe0["fe0 :8080<br/>nginx (prod)"]
be0["be0 :4402<br/>FastAPI"]
pg[("postgres :5432")]
s3["minio :9000 API<br/>:9001 console"]
end
U -->|"http://PUBLIC_HOST:FE_PORT (not https)"| FEPORT --> fe0
U -->|"presigned GET bytes"| MINAPI --> s3
fe0 -->|"nginx proxy /api, /submitted-initiatives…"| be0
be0 -->|"INITIATIVE_DATABASE_URL"| pg
be0 -->|"S3_ENDPOINT_URL presign + server ops"| s3
subgraph oneoff [runs after MinIO healthy]
cors["minio-cors:<br/>ensure buckets"]
end
cors --> s3
subgraph localhost_only [Reachable only on the host VM]
dbmap["127.0.0.1:15432 → postgres"]
bemap["127.0.0.1:4402 → be0"]
end
2. Responsibility matrix
| Piece | Compose service | Listen (container) | Published to Internet? | Env / config that ties it together |
|---|---|---|---|---|
| Frontend | fe0 |
0.0.0.0:8080 |
http://${PUBLIC_HOST}:${FE_PORT} maps host → container 8080 |
Production: Dockerfile.prod → nginx static + proxy /api → be0. Dev: Vite (Dockerfile + npm run dev) |
| Backend API | be0 |
0.0.0.0:4402 |
No — 127.0.0.1:4402 on host only; browsers use fe0 proxy /api, etc. |
INITIATIVE_DATABASE_URL=postgresql+asyncpg://…@postgres:5432/…, S3_ENDPOINT_URL=http://minio:9000, S3_PUBLIC_ENDPOINT_URL defaults to http://${PUBLIC_HOST}:${MINIO_API_PORT} — set https://… when MinIO sits behind HTTPS for presigned URLs (minio-behind-https.md) |
| Database | postgres |
:5432 |
No — 127.0.0.1:15432 on host for admin tools only |
POSTGRES_* first init only (initiative_pg_data volume) |
| Object storage | minio |
API :9000, console :9001 |
http://${PUBLIC_HOST}:${MINIO_API_PORT} (often proxied via HTTPS in production — see linked doc above) MINIO_CONSOLE_PORT |
MINIO_SERVER_URL / MINIO_BROWSER_REDIRECT_URL compile from PUBLIC_HOST by default or use .env HTTPS overrides together with S3_PUBLIC_ENDPOINT_URL |
minio-cors is a one-shot job: waits for healthy MinIO and creates initiative-attachments, initiative-exports, and initiative-quarantine. Community MinIO does not implement S3 per-bucket CORS (mc cors set); browsers rely on MINIO_API_CORS_ALLOW_ORIGIN on the minio service (defaults to * in Compose) for presigned GETs.
3. Request paths (mental trace)
-
SPA + API calls
User openshttp://PUBLIC_HOST:FE_PORT. The browser loads Vite-served assets fromfe0. Calls to/api/...(and similar proxied paths) go tofe0, which forwards tohttp://be0:4402inside the network. -
Presigned S3 / MinIO from the browser
be0builds URLs usingS3_PUBLIC_ENDPOINT_URL(must be reachable from the user’s browser, usuallyhttp://PUBLIC_HOST:MINIO_API_PORT). The browser downloads objects directly from MinIO on the host-published port—not throughbe0. -
Backend → Postgres
Onlybe0usesINITIATIVE_DATABASE_URL; host127.0.0.1:15432is optional forpsql/ dumps from the VPS shell. -
Backend → MinIO (server-side)
be0usesS3_ENDPOINT_URL=http://minio:9000for signing and internal API traffic;miniois the Compose DNS name, notPUBLIC_HOST.
4. Startup order Compose enforces
| Order | Service | Blocking condition |
|---|---|---|
| 1 | postgres, minio |
(none in compose—they start in parallel.) |
| 2 | minio-cors |
minio healthy |
| 3 | be0 |
postgres healthy AND minio healthy |
| 4 | fe0 |
be0 started |
If Postgres never becomes healthy (bad POSTGRES_* vs existing volume, etc.), be0 never attaches cleanly and fe0 may misbehave or appear “up” while API calls fail.
5. Deploy checklist (recommended)
From the repository root on the VPS (same folder as docker-compose.prod.yml):
.envpresent (ls -a), values filled from.env.example.PUBLIC_HOST= the hostname or IP users type in the browser (must match how you open the UI and how MinIO URLs are generated)../scripts/verify-prod-env.shexits0.- Start the stack (pick one):
- Script (pull, build, detached):
./scripts/deploy-prod.sh - Manual compose: see subsection 5.1 below.
- Script (pull, build, detached):
- Open app with
http://, nothttps://, unless you put a reverse proxy in front.
5.1 Manual docker compose -f docker-compose.prod.yml up
This is valid as long as you stay in the repo root and a .env file exists there.
- Variable substitution: Compose automatically reads a file named
.envin the project directory (normally your current working directory) and uses it to expand${PUBLIC_HOST},${FE_PORT}, etc. indocker-compose.prod.yml. You do not have to pass--env-file .envfor that to work, but being explicit avoids surprises:docker compose --env-file .env -f docker-compose.prod.yml up -d --build - Foreground vs daemon: plain
upstreams logs in the terminal and exits with Ctrl+C (containers stop unless you use--abort-on-container-exitbehavior—default stops on interrupt). For a long-running server, preferup -d(detached). - Rebuild after Dockerfile or dependency changes: add
--build(the deploy script always builds). Without it, Compose may reuse old images. - No pre-checks: the script runs
verify-prod-env.shandcompose configfor you; if you use onlyup, run./scripts/verify-prod-env.shyourself first so badPOSTGRES_USER/ empty ports fail fast.
Example minimal manual flow:
cd /path/to/remix-of-my-perspective-lifestyle-32
./scripts/verify-prod-env.sh
docker compose --env-file .env -f docker-compose.prod.yml up -d --build
docker compose --env-file .env -f docker-compose.prod.yml ps
6. Quick verification commands
Run on the host with the same --env-file you use for deploy:
docker compose --env-file .env -f docker-compose.prod.yml ps
docker compose --env-file .env -f docker-compose.prod.yml logs --tail=80 postgres
docker compose --env-file .env -f docker-compose.prod.yml logs --tail=80 be0
docker compose --env-file .env -f docker-compose.prod.yml logs --tail=40 fe0
docker compose --env-file .env -f docker-compose.prod.yml logs --tail=40 minio
Smoke checks:
- Postgres:
docker compose ... exec postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"(substitute real values from.envwhen using shell snippets). - Backend (from host):
curl -sS http://127.0.0.1:4402/docs— expect FastAPI Swagger HTML (or/openapi.json). - MinIO (from host or laptop if firewall allows):
curl -sS -o /dev/null -w "%{http_code}" http://${PUBLIC_HOST}:${MINIO_API_PORT}/minio/health/live.
7. Firewall hint
Typically you must allow inbound TCP: FE_PORT, MINIO_API_PORT, MINIO_CONSOLE_PORT (and 22 for SSH). Postgres and be0 intentionally stay on localhost-only binds in this compose file.