# 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<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](./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.
