Deployment
Production deployment uses compose.prod.yml. Traefik is used as the reverse proxy (expected to be running as a shared external service on the host). No Nginx is involved.
API and frontend are exposed on separate subdomains via Traefik labels with automatic Let's Encrypt TLS.
Services
| Service |
Image |
Role |
api |
registry.gabrielkaszewski.dev/thoughts:latest |
API server on port 8000 |
worker |
Same image, entrypoint ./thoughts-worker |
Event consumer — AP fan-out, notifications |
frontend |
registry.gabrielkaszewski.dev/thoughts-frontend:latest |
Next.js SSR on port 3000 |
database |
postgres:16-alpine |
PostgreSQL with persistent volume |
NATS runs as a shared external service (k_nats) — not managed by this compose file in production.
Networks
| Network |
Type |
Purpose |
internal |
bridge |
DB ↔ API ↔ worker (isolated) |
shared-services |
external |
Access to shared infra (NATS, etc.) |
traefik |
external |
Traefik can reach the containers |
Steps
1. Clone and configure
BASE_URL must be the public API domain (e.g. https://api.thoughts.example.com). ActivityPub IDs are derived from it.
2. Pull images and start
Migrations run automatically when the api container starts.
Traefik Routing
Routing is configured via Docker labels on each container. Traefik must be running with:
- A
traefik Docker network
- A
letsencrypt cert resolver configured
The api container is routed to api.<your-domain>, the frontend to <your-domain>.
Updating
Migrations run on next API container start — no manual step needed.
Data Persistence
PostgreSQL data lives in a named Docker volume (postgres_data). Safe across docker compose down and image updates. Only destroyed with docker compose down -v.
Environment Variables Reference
| Variable |
Required |
Description |
POSTGRES_USER |
Yes |
PostgreSQL username |
POSTGRES_PASSWORD |
Yes |
PostgreSQL password |
POSTGRES_DB |
Yes |
PostgreSQL database name |
JWT_SECRET |
Yes |
Secret for JWT signing |
BASE_URL |
Yes |
Public API base URL (used for AP IDs) |
CORS_ORIGINS |
No |
Allowed CORS origins (default: *) |
ALLOW_REGISTRATION |
No |
Enable public registration (default: false) |