Files
travel-planner/docs/architecture/deployment.md
2026-08-17 15:54:30 +02:00

41 lines
3.2 KiB
Markdown

# Deployment Architecture
## Single published port
Production Docker Compose (`compose.yml`) publishes **exactly one** host port: `edge` (Nginx), mapped via `APP_HTTPS_PORT` (default `443`). No other service (`api`, `worker`, `postgres`, `redis`) defines a Compose `ports:` mapping — they are reachable only over the internal `travel` bridge network via their service DNS names. `scripts/verify-compose-invariants.mjs` (wired into `pnpm test:compose`) fails the build if this invariant regresses.
## TLS
The edge container listens on container port 443 only and requires a certificate and private key mounted at the paths configured by `TLS_CERT_FILE`/`TLS_KEY_FILE` (bind-mounted read-only to `/run/tls/tls.crt` and `/run/tls/tls.key`). There is no port-80 fallback in this phase.
## Internal reachability
- PostgreSQL and Redis are not reachable from the host through Compose-published ports; only containers on the `travel` network can reach them.
- The API is not reachable from the host directly; all external traffic reaches it through `edge`'s `/api/` and `/health/` proxy locations.
- The worker process has neither `ports` nor `expose` — it accepts no inbound traffic at all.
- Outbound egress from `api` and `worker` (e.g. to Mistral, web research, SMTP providers in later phases) remains allowed.
## Image tags
TeamCity supplies immutable `IMAGE_TAG` values (see `scripts/teamcity/build-images.sh`); `latest`/floating tags are refused. `REGISTRY` and `IMAGE_TAG` together select the exact image digest-equivalent tag deployed to a host.
## TeamCity wiring
| TeamCity stage | Repository entry point |
|------------------------|----------------------------------|
| Validate | `scripts/teamcity/validate.sh` |
| Build + Push | `scripts/teamcity/build-images.sh` |
| Deploy over SSH | `scripts/teamcity/deploy.sh` |
| Post-deploy smoke | `scripts/teamcity/smoke.sh` |
| Rollback | `scripts/teamcity/rollback.sh` |
The existing TeamCity project configures these as command-line/SSH build steps; all deployment logic stays in version control, not in TeamCity step configuration.
## OIDC configuration (Phase 02+)
`api` and `worker` both require `OIDC_ISSUER` and `OIDC_AUDIENCE` at startup (validated fail-fast by `loadEnvironment`, same as `DATABASE_URL`/`REDIS_URL`). Neither is a secret — this is a public PKCE client with no client secret. The Angular production bundle bakes `OIDC_ISSUER`/`OIDC_CLIENT_ID` in at **image build time** via `docker/edge.Dockerfile` build args (sourced from the `OIDC_ISSUER`/`OIDC_CLIENT_ID` environment variables passed to `docker compose build`), not at container runtime, since static frontend assets cannot read server-side environment variables after the fact.
## Migrations (Phase 02+)
`backend/apps/api/src/migration.ts` now runs real, versioned `node-pg-migrate` migrations from `backend/migrations/`; the Phase 01 no-op body has been replaced. The container command contract (`node backend/dist/apps/api/src/migration.js`) is unchanged, so `scripts/teamcity/deploy.sh` required no changes. Migrations are copied into the `travel-api` image so `docker compose run --rm --no-deps api node backend/dist/apps/api/src/migration.js` has everything it needs.