Skip to content

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

ContainerPort (host)Image source
postgres5432docker/postgres.Dockerfile
redis6379redis:7-alpine
backoffice-api8001apps/backoffice-api/Dockerfile
main-api8002apps/main-api/Dockerfile
media-api8003apps/media-api/Dockerfile
backoffice-web5001apps/backoffice-web/Dockerfile
main-web5002apps/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

bash
# 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 down

Required Secrets

Set these in your environment before running docker compose up:

bash
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-photo

WARNING

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:

json
[
  {
    "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.

Acme Photo Platform — Internal Documentation