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

3.2 KiB

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.