Initial commit: YouTube Studio Flow (backend, frontend, infrastructure, docs)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# 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 `traefik-network` external 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:
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user