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
travelnetwork 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
portsnorexpose— it accepts no inbound traffic at all. - Outbound egress from
apiandworker(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.