Deployment
The platform ships as Docker images orchestrated by docker-compose.yml. Each service is built from its own Dockerfile in the monorepo root build context.
Services
| Container | Port (host) | Image source |
|---|---|---|
| postgres | 5432 | docker/postgres.Dockerfile |
| redis | 6379 | redis:7-alpine |
| backoffice-api | 8001 | apps/backoffice-api/Dockerfile |
| main-api | 8002 | apps/main-api/Dockerfile |
| media-api | 8003 | apps/media-api/Dockerfile |
| backoffice-web | 5001 | apps/backoffice-web/Dockerfile |
| main-web | 5002 | apps/main-web/Dockerfile |
Migrations run automatically as a one-shot migrate service that exits after applying pending migrations. All API services wait for it via depends_on: condition: service_completed_successfully.
Build & Start
# Build all images and start
docker compose up --build -d
# Rebuild and restart a single service
docker compose up --build media-api -d
# View logs
docker compose logs -f media-api
# Stop everything
docker compose downRequired Secrets
Set these in your environment before running docker compose up:
export DATABASE_URL=postgresql://acme:acme@postgres:5432/acme_photo
export JWT_SECRET=<min 32 chars>
export STRIPE_SECRET_KEY=sk_live_...
export STRIPE_WEBHOOK_SECRET=whsec_...
export RESEND_API_KEY=re_...
export R2_ACCOUNT_ID=...
export R2_ACCESS_KEY_ID=...
export R2_SECRET_ACCESS_KEY=...
export R2_BUCKET_NAME=...
export R2_PUBLIC_URL=https://<account-id>.r2.cloudflarestorage.com
export AWS_REKOGNITION_REGION=us-east-1
export AWS_REKOGNITION_COLLECTION_PREFIX=acme-photoWARNING
Never commit .env files. Only .env.example files belong in the repo.
Cloudflare R2 — CORS
For browser direct uploads (presigned PUT URLs) to work, configure a CORS policy on your R2 bucket:
[
{
"AllowedOrigins": ["https://yourdomain.com"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["Content-Type"],
"MaxAgeSeconds": 3600
}
]Set this in the Cloudflare dashboard under R2 → your bucket → Settings → CORS Policy.
Stripe Webhook
Register your webhook endpoint in the Stripe dashboard:
- URL:
https://your-main-api-domain/v1/stripe-webhook - Events:
payment_intent.succeeded,payment_intent.payment_failed
Copy the signing secret to STRIPE_WEBHOOK_SECRET.