This guide documents backend deployment and runtime configuration for local and production environments.
- Docker and Docker Compose
- Python 3.12+ (for local non-Docker runs)
- PostgreSQL credentials, RabbitMQ credentials, and object storage credentials (MinIO for dev or external S3/GCS for prod)
Use the production template from the repo root:
cp .env.production.example .env.productionSet at least:
POSTGRES_PASSWORDSECRET_KEYDOMAINACME_EMAILRABBITMQ_URLandRABBITMQ_DEFAULT_PASS(microservices prod)- External S3:
S3_ENDPOINT_URL,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_MEDIA_BUCKET,S3_MODELS_BUCKET - Google Sign-In (optional):
GOOGLE_CLIENT_IDandVITE_GOOGLE_CLIENT_ID— same OAuth Client ID; see credentials-setup.md. Leave empty to disable. SetGOOGLE_AUTH_ENABLED=trueto require both at deploy time.
Backend dependencies are split by concern:
- Core API/runtime:
uv sync - AI workers (STT/NMT/TTS):
uv sync --group ai - Dev/test tooling:
uv sync --group dev
In Docker, the backend API uses INSTALL_AI=false. AI inference runs in stt-service, nmt-service, and tts-service.
Start the production microservices stack:
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml up -d --buildThis stack includes: caddy, postgres, rabbitmq, orchestrator, stt-service, nmt-service, tts-service, media-service, backend.
The dubbing pipeline runs over RabbitMQ (not Celery). Backend publishes job.created after media preprocess; the Go orchestrator coordinates STT → NMT → TTS → merge.
RabbitMQ management UI: https://rabbitmq.$DOMAIN (Caddy reverse proxy; credentials = RABBITMQ_DEFAULT_USER / RABBITMQ_DEFAULT_PASS). When Terraform DNS is enabled, terraform output rabbitmq_fqdn shows the FQDN.
Check health:
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml ps
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml logs backend --tail=100Caddy reads the committed Caddyfile.production (app, RabbitMQ, and Grafana site blocks).
Set COMPOSE_PROFILES=observability in .env.production to start the LGTM stack. Both compose files are always referenced; the profile controls observability containers only. See observability.md.
Prerequisites:
- DNS:
grafana.app.$ZONE→ VM IP (terraform output grafana_fqdn;dns_include_grafanadefaults totrue) - Secrets in
.env.production:
COMPOSE_PROFILES=observability
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=<strong>Manual start (same compose selection as deploy):
source infra/scripts/lib/compose-env.sh
$COMPOSE up -d --buildURLs after deploy:
| UI | URL |
|---|---|
| Grafana | https://grafana.$DOMAIN |
| RabbitMQ | https://rabbitmq.$DOMAIN |
See observability.md for dashboards, LogQL, and alerting.
After Terraform provisions a GPU VM, enable the Compose GPU overlay. Recommended: g2-standard-8 with embedded L4 (gpu_count = 0 in Terraform). Legacy: n1-standard-8 + T4 (gpu_count = 1, Frankfurt zone europe-west3-b only).
COMPOSE_ENABLE_GPU=true
STT_DEVICE=cuda
STT_COMPUTE_TYPE=float16
OMNIVOICE_DEVICE=cuda
OMNIVOICE_DTYPE=float16
NMT_DEFAULT_NUM_BEAMS=2See infra/terraform/env-production.gpu.snippet for a copy-paste block into Secret Manager env-production.
Deploy merges docker-compose.microservices.gpu.yml automatically when COMPOSE_ENABLE_GPU=true (see infra/scripts/lib/compose-env.sh).
Post-deploy validation on the VM:
bash infra/scripts/validate-gpu-deploy.shBenchmark before/after: inference_benchmark_baseline.md.
Rollback to CPU: set COMPOSE_ENABLE_GPU=false, STT_DEVICE=cpu, OMNIVOICE_DEVICE=cpu, redeploy.
Full host/driver details: gpu_setup.md. Terraform quota/resize: infra/terraform/README.md.
The deploy-gcp workflow auto-deploys main only. Before expecting GPU inference in production:
- Merge GPU changes to
main(pushes to feature branches do not deploy). - Update GitHub secret
GCP_VM_HOSTafter a VM region/IP change (e.g. Frankfurt migration). - Merge
infra/terraform/env-production.gpu.snippetinto Secret Managerenv-production(or the VM.env.production), then redeploy. - Remove duplicate keys in
env-production— especiallySTT_DEVICE(last line wins; a trailingSTT_DEVICE=cpudisables GPU STT). - Optional: set repository secret
GCP_GPU_ENABLED=trueto run post-deploy GPU smoke in the workflow summary (warn-only; deploy still succeeds if containers are on CPU).
Host GPU preflight runs automatically during deploy when COMPOSE_ENABLE_GPU=true. Container CUDA checks run via validate-gpu-deploy.sh (warn-only during deploy; use --strict for manual debugging).
The Celery-based stack remains for rollback only:
docker compose --env-file .env.production -f docker-compose.prod.minimal.yml up -d --buildThis stack keeps: caddy, postgres, redis, backend, celery-worker-ai, flower. It does not run the RabbitMQ orchestrator or microservices and cannot complete fullDubbing via the new pipeline.
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --buildApply migrations after deploy:
docker compose exec backend alembic upgrade headProduction deploys run via .github/workflows/deploy-gcp.yml, which SSHs to the VM and pipes infra/scripts/deploy-production.sh (and git-sync.sh on first boot).
First deploy after terraform apply: wait for VM bootstrap (/var/lib/vm-bootstrap.done), set GitHub secrets (GCP_VM_HOST, GCP_VM_USER, GCP_VM_SSH_KEY), then run the workflow. Bootstrap writes .env.production to /var/lib/dabljaar/; the deploy script clones into ~/web and copies env into the checkout.
- Git sync —
git fetch --prune origin mainthengit checkout -B main $GITHUB_SHA(VM stays on branchmain, not detached HEAD) - Lock —
flockprevents overlapping deploys (~/web/.deploy.lockor/var/lock/dabljaar-deploy.lock) - Frontend — atomic build to
frontend/dist.next, then swap intofrontend/dist(Caddy never serves an empty/srv) - Validate —
caddy validatebefore touching the running stack - Infra + migrate —
postgres+rabbitmq, build backend once, run Alembic in a one-off container - Reconcile —
docker compose up -d --build --remove-orphans --wait - Edge — recreate Caddy (
compose up -d caddy), verify HTTPS API + SPA routes
Deploy logs append to ~/web/deploy.log. On failure, service logs and compose ps -a are printed automatically.
cd ~/web
export DEPLOY_SHA=<commit-sha> # required
export REPO_FALLBACK=git@github.com:ORG/REPO.git # only needed for first clone
bash infra/scripts/deploy-production.shIf the VM was deployed before this change and shows (HEAD detached at …):
cd ~/web
git fetch --prune origin main
git checkout -B main "$(git rev-parse HEAD)"After deploy:
cd ~/web
git branch --show-current # expect: main
git rev-parse HEAD # expect: matches GITHUB_SHA
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml ps
# caddy + backend should be healthy even while tts-service is still starting
curl -fsS "https://$(grep ^DOMAIN= .env.production | cut -d= -f2)/api/health"
curl -fsSI "https://$(grep ^DOMAIN= .env.production | cut -d= -f2)/" | grep -i strict-transport-securityRe-run the deploy script without code changes to confirm idempotency — the second run should succeed without downtime.
The website (caddy → backend → SPA) starts as soon as backend is healthy. AI workers (stt, nmt, tts, media) and the orchestrator can still be warming up — dubbing jobs queue in RabbitMQ until workers pass /readiness. Caddy no longer depends on RabbitMQ health.
If GET / returns 404 with only Server: Caddy and no Strict-Transport-Security header, Caddy is not applying the {$DOMAIN} site block from Caddyfile.minimal (DNS is usually fine).
On the VM (cd ~/web):
# Broken: no Strict-Transport-Security. Working: header present.
curl -sI https://app.dabljaar.tech/ | head -20
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml ps -a
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml exec caddy env | grep -E 'DOMAIN|ACME'
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml exec caddy test -f /srv/index.html && echo OK || echo MISSING
ls -la frontend/dist/index.html
tail -200 ~/web/deploy.logRecovery:
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml up -d --build --remove-orphans
docker compose --env-file .env.production -f docker-compose.microservices.prod.yml up -d caddy
curl -sI https://app.dabljaar.tech/ | grep -i strictOr re-run the full deploy script:
cd ~/web && DEPLOY_SHA=$(git rev-parse HEAD) bash infra/scripts/deploy-production.shEnsure DATABASE_URL credentials and the postgres service credentials match exactly. Confirm POSTGRES_PASSWORD is set in the environment file used by compose.
Check:
- Migration errors in backend logs
- API reachability on
/api/health - Postgres and RabbitMQ readiness
In GitHub Actions deploys, backend and caddy logs are dumped automatically on health-check failure.
Verify:
- RabbitMQ healthy (
docker compose ps rabbitmq) - Orchestrator running and connected
- Stage workers passing
/readiness(STT/TTS cold start can take several minutes on first deploy)
Standalone POST /api/tts/synthesize proxies to tts-service via TTS_SERVICE_URL — no Celery worker required.
Symptoms: STT child job is COMPLETED, parent stays at ~25%, NMT never starts. Orchestrator logs may show Duplicate terminal result — ignoring (fixed in orchestrator — redeploy orchestrator first).
Recover a stuck job after orchestrator fix is deployed:
# From repo root on prod VM
chmod +x infra/scripts/recover-pipeline-after-stt.sh
./infra/scripts/recover-pipeline-after-stt.sh <stt_child_job_id>Or redeploy orchestrator only:
docker compose --env-file .env.production \
-f docker-compose.microservices.prod.yml \
up -d --build orchestratorSTT logs may show No objects found at s3://... or missing model.bin/config.json. Verify bucket layout:
chmod +x infra/scripts/verify-whisper-s3-model.sh
./infra/scripts/verify-whisper-s3-model.shRequired keys under STT_MODEL_KEY (default whisper-medium/):
model.binconfig.json
Align S3_MODELS_BUCKET with terraform output storage_bucket_name. Set STT_ALLOW_HF_FALLBACK=false once S3 cache is valid.
- Sync git to target commit (
git checkout -B main <sha>) - Run
infra/scripts/deploy-production.sh(handles frontend build, migrations, compose reconcile, health gates) - Verify worker
/readinessfor pipeline jobs (site can be up before workers finish cold start)
See also: microservices_migration.md, microservices_lld.md.