# 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](./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`**. ```mermaid 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
nginx (prod)"] be0["be0 :4402
FastAPI"] pg[("postgres :5432")] s3["minio :9000 API
: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:
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](./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) 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 user’s 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. --- ## 5. Deploy checklist (recommended) 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: ```bash 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: ```bash 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: ```bash 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.