Files
sciagent/docs/deploy-stack-overview.md
T
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

151 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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.
---
## 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.