Files
sciagent/docs/deploy-stack-overview.md
Thinh Lam 688fac73e9
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped
sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:38:30 +07:00

8.0 KiB
Raw Permalink Blame History

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 /apibe0. Dev: Vite (Dockerfile + npm run dev)
Backend API be0 0.0.0.0:4402 No127.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 No127.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)

  1. SPA + API calls
    User opens http://PUBLIC_HOST:FE_PORT. The browser loads Vite-served assets from fe0. Calls to /api/... (and similar proxied paths) go to fe0, which forwards to http://be0:4402 inside the network.

  2. Presigned S3 / MinIO from the browser
    be0 builds URLs using S3_PUBLIC_ENDPOINT_URL (must be reachable from the users browser, usually http://PUBLIC_HOST:MINIO_API_PORT). The browser downloads objects directly from MinIO on the host-published port—not through be0.

  3. Backend → Postgres
    Only be0 uses INITIATIVE_DATABASE_URL; host 127.0.0.1:15432 is optional for psql / dumps from the VPS shell.

  4. Backend → MinIO (server-side)
    be0 uses S3_ENDPOINT_URL=http://minio:9000 for signing and internal API traffic; minio is the Compose DNS name, not PUBLIC_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.


From the repository root on the VPS (same folder as docker-compose.prod.yml):

  1. .env present (ls -a), values filled from .env.example.
  2. PUBLIC_HOST = the hostname or IP users type in the browser (must match how you open the UI and how MinIO URLs are generated).
  3. ./scripts/verify-prod-env.sh exits 0.
  4. Start the stack (pick one):
    • Script (pull, build, detached): ./scripts/deploy-prod.sh
    • Manual compose: see subsection 5.1 below.
  5. Open app with http://, not https://, 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 .env in the project directory (normally your current working directory) and uses it to expand ${PUBLIC_HOST}, ${FE_PORT}, etc. in docker-compose.prod.yml. You do not have to pass --env-file .env for 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 up streams logs in the terminal and exits with Ctrl+C (containers stop unless you use --abort-on-container-exit behavior—default stops on interrupt). For a long-running server, prefer up -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.sh and compose config for you; if you use only up, run ./scripts/verify-prod-env.sh yourself first so bad POSTGRES_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 .env when 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.