Files
youtube-studio-flow/documentation/YouTube Studio Flow/05 - Development/05 - Deployment and Operations.md
T
devil ba8c7185a7 fix(infrastructure): use the actual Traefik network name (proxy)
The external network on the target server is named "proxy", not
traefik-network as originally assumed - deploy was failing with
"network traefik-network not found". Updated the compose file and
both the infra README and vault deployment doc to match.
2026-08-11 15:43:06 +02:00

4.9 KiB

Deployment and Operations


Production Stack

The production setup is a single-host Docker Compose deployment using Traefik as a reverse proxy. There is no Kubernetes, cloud-managed infrastructure, or horizontal scaling configuration.

Services (infrastructure/docker-compose.yml):

Service Image Role
migrate backend Dockerfile Runs prisma migrate deploy once before API starts
api backend Dockerfile NestJS HTTP API on port 3001
worker backend Dockerfile BullMQ queue processor (node dist/worker.js)
frontend frontend Dockerfile Next.js on port 3000
postgres postgres:16-alpine Database — bound to 127.0.0.1:5432 (not public)
redis redis:7-alpine BullMQ queues — noeviction policy, AOF persistence

SSL / routing: Traefik handles TLS termination with automatic Let's Encrypt certificates. Both API (/api prefix) and frontend run on the same domain — Traefik routes by path prefix. The proxy external network (the host's existing Traefik network) must exist before deploy.

Build: Multi-stage Dockerfile (node:22-alpine). Builder compiles TypeScript; runner installs prod-only deps. The Prisma CLI is copied from builder stage so the migrate service can run schema migrations.


Secrets Management

All secrets are passed as environment variables from infrastructure/.env (based on .env.example). There is no secrets vault, no encrypted secret store, and no runtime secret injection. The .env file must be present on the host before docker compose up.

Secrets that must be set:

Variable Notes
TOKEN_ENCRYPTION_KEY Exactly 32 characters. AES-256 key for YouTube OAuth tokens. See rotation note below.
JWT_SECRET / JWT_REFRESH_SECRET Generate with openssl rand -base64 48
POSTGRES_PASSWORD / REDIS_PASSWORD Strong random passwords
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET From Google Cloud Console

Health Check

GET /api/v1/health returns { "status": "ok" }. This is a shallow check — it only confirms the Node process is responding; it does not verify database connectivity or Redis availability. Docker healthcheck polls this endpoint every 15 s; the frontend service waits for the API to be healthy before starting.


Logging

No structured logging is configured. The application uses NestJS's default logger (console.log/console.error). In production, logs go to stdout and are captured by Docker's logging driver (default: json-file). There is no log aggregation, no Sentry integration, and no OpenTelemetry instrumentation.


Job Failure Handling

BullMQ jobs do not have automatic retry configured except where noted:

Queue Retry Failed job retention
youtube-sync None Last 5 failed jobs kept (removeOnFail: { count: 5 }); completed jobs removed
render None BullMQ default (kept until manually cleared)
lint None BullMQ default
bulk-metadata None BullMQ default
import None BullMQ default
conflict-detection None Last 10 failed jobs kept (removeOnFail: 10); last 10 completed kept (removeOnComplete: 10). Registered as a BullMQ repeatable by ConflictDetectionScheduler when CONFLICT_DETECTION_ENABLED=true.

There is no dead-letter queue and no mechanism to notify users when a background job fails. A failed sync job means the user's video was not pushed to YouTube — the UI will continue to show "push pending" with no error indication. Failed jobs can be inspected directly in Redis or via a BullMQ dashboard (none is currently deployed).


Rate Limiting

There is no rate limiting on any API endpoint. @nestjs/throttler is not installed. All routes are unthrottled.


Database Backups

No backup strategy is configured. PostgreSQL data lives in the postgres_data Docker volume on the host. There is no automated backup job, no pg_dump schedule, and no offsite backup.

To back up manually:

docker exec <postgres_container> pg_dump -U $POSTGRES_USER $POSTGRES_DB > backup.sql

TOKEN_ENCRYPTION_KEY Rotation

YouTube OAuth tokens are AES-256 encrypted in the database using TOKEN_ENCRYPTION_KEY. Changing this key breaks all channel connections — all stored tokens become unreadable and every connected channel must be fully re-authenticated by its owner.

There is no tooling for key rotation. If rotation is required (e.g. key compromise), the procedure is:

  1. Take the application offline.
  2. Write a one-off migration script that decrypts every Channel.accessToken and Channel.refreshToken with the old key and re-encrypts them with the new key.
  3. Update TOKEN_ENCRYPTION_KEY in .env.
  4. Bring the application back online.

Until such a script is written, key rotation = forced re-authentication for all channel owners. See also the 01 - Technical Debt and Future Work backlog.