Docker Compose Deployment
Two compose files ship in the repository root:
docker-compose.prod.yml— a production-shaped single-host stack: PostgreSQL, Redis, a one-shot migrator, the API, Celery workers, Celery beat, and the SPA behind nginx.docker-compose.dev.yml— the development stack for working on Abrq DIP itself.
For offline installs from the vendor-shipped bundle (pre-built image tarballs, no build step), see air-gapped installation — it is the offline variant of this path.
Dev vs prod at a glance#
| Aspect | docker-compose.dev.yml |
docker-compose.prod.yml |
|---|---|---|
ABRQ_ENV |
dev (license check bypassed, relaxed guards) |
prod (license verified at startup, HSTS on) |
| Images | Dev images with the backend source bind-mounted; hot reload | Production multi-stage images built from backend/Dockerfile and frontend/Dockerfile |
| Migrations | Backend container runs alembic upgrade head inline before starting uvicorn |
Dedicated one-shot migrator service; app services wait for its successful completion |
| PostgreSQL | Port 5432 published to the host; init scripts also create a source_demo fixture database |
No published port; data on a named volume only |
| Redis | Loopback-only published port (127.0.0.1:6379), no auth |
No published port, --requirepass enforced (REDIS_PASSWORD required), AOF persistence |
| Secrets | Baked-in dev defaults (master key, JWT secret, DB password) | Must come from .env; several have fail-fast guards |
| Worker | One worker, concurrency 2 | ${ABRQ_WORKER_REPLICAS:-2} replicas, concurrency ${ABRQ_CELERY_CONCURRENCY:-4}, 60s stop grace |
| Beat schedule state | Throwaway (/tmp) |
beat_state named volume; replicas: 1 pinned |
| Frontend | Vite dev server on port 5173 | nginx on ${ABRQ_FRONTEND_PORT:-8080}:80 |
| Backups dir init | — | backup_dir_init one-shot fixes ownership of the backups bind-mount |
Both stacks bind-mount ./backups (override with ABRQ_BACKUP_HOST_DIR
in prod) into the containers at /var/abrq-dip/backups — a host
bind-mount, not a named volume, precisely so docker compose down -v
cannot wipe your backups.
Note. In both compose files the datastore database (CDC mirror landings) is named
datastore, created by the init scripts indocker/postgres-init/. The Helm chart names the same databaseabrq_dip_datastore— the two paths are not drop-in interchangeable at the database level.
Production walkthrough#
1. Prepare .env#
Compose interpolates variables from a .env file next to the compose
file. Required — the stack fails fast with a named error if a guarded
variable is unset:
| Variable | Guarded | Purpose |
|---|---|---|
POSTGRES_PASSWORD |
no guard — do not leave unset | Password for the bundled PostgreSQL and the app connection URLs. |
REDIS_PASSWORD |
yes: set REDIS_PASSWORD in your .env — the prod broker must require auth |
Redis --requirepass value, wired into ABRQ_REDIS_URL. |
ABRQ_MASTER_KEY |
no guard — required by the backend at startup | Fernet master key. |
ABRQ_JWT_SECRET |
no guard — required by the backend at startup | Session-token signing secret. |
ABRQ_INITIAL_ADMIN_PASSWORD |
yes: override the default admin password |
First-boot admin seed; production refuses the literal default. |
ABRQ_CORS_ALLOWED_ORIGINS |
yes: set the SPA origin(s) for CORS |
Exact UI origin(s), comma-separated. |
Optional knobs: ABRQ_GUNICORN_WORKERS (default 2),
ABRQ_CELERY_CONCURRENCY (default 4), ABRQ_WORKER_REPLICAS (default
2), ABRQ_FRONTEND_PORT (default 8080), ABRQ_BACKUP_HOST_DIR (default
./backups). Generation commands for the secrets are in
choosing a deployment path.
2. Place the license file#
Production startup verifies the vendor-signed license. Make
license.json available at ABRQ_LICENSE_FILE (default
/var/abrq-dip/license.json) inside the backend, worker, and beat
containers — see installing a license.
(The vendor release bundle's compose file wires this mount for you; see
air-gapped installation.)
3. Build and start#
docker compose -f docker-compose.prod.yml up -d --build
Startup order is dependency-driven: PostgreSQL and Redis become healthy;
the one-shot migrator applies alembic upgrade head and exits 0; the
one-shot backup_dir_init fixes ownership of the backups directory
(Docker auto-creates missing bind-mount directories as root, but the app
runs as uid 1000 — without this fix, the default-on auto-backup would fail
with a permission error on every fresh install); then backend, workers,
beat, and frontend start.
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f backend
4. Retrieve the initial admin password file#
On first boot against an empty users table, the backend seeds the initial
admin user and writes the password to a file (mode 0600) at
ABRQ_INITIAL_PASSWORD_FILE (default /var/abrq-dip/initial-password.txt,
on the initial_password named volume):
docker compose -f docker-compose.prod.yml exec backend \
cat /var/abrq-dip/initial-password.txt
Open http://<HOST>:8080 (or your ABRQ_FRONTEND_PORT), log in, and
complete the forced password change.
Health checks#
Every long-running service carries a compose healthcheck; depends_on
conditions chain them so the stack comes up in order.
| Service | Check | Notes |
|---|---|---|
framework_db |
pg_isready |
5s interval. |
redis |
authenticated redis-cli ping |
Uses REDIS_PASSWORD. |
backend |
curl -fsS http://localhost:8000/health |
/health requires the framework DB and Redis to answer; 200 ok, 503 degraded. |
worker |
celery inspect ping against the container's own worker |
Proves broker connectivity, not just a live process. |
beat |
pgrep for the beat process |
Beat is a scheduler, not a consumer — there is no broker-side ping for it. |
frontend |
none (prod) | nginx serves static files and proxies /api to the backend service. |
Failure modes#
| Symptom | Likely cause | Fix |
|---|---|---|
docker compose up aborts naming REDIS_PASSWORD, the admin password, or CORS |
A guarded variable is unset | Fill in .env (see the table above) and re-run. |
Backend exits immediately; logs show startup_config_error |
Missing/invalid ABRQ_MASTER_KEY or ABRQ_JWT_SECRET, or the admin password left at the literal default |
Set real values in .env; the log lists every problem found. |
Backend exits; logs mention license_check_failed |
License file missing at /var/abrq-dip/license.json, invalid signature, or expired |
Place a valid license.json mount; see installing a license. |
migrator exits non-zero |
Schema mismatch from a previous half-applied upgrade | docker compose -f docker-compose.prod.yml logs migrator for the alembic error; see upgrade and rollback for the recovery path. |
framework_db refuses connections after a re-install |
Old data volume initialized with a different POSTGRES_PASSWORD |
Either restore the old password, or docker compose down -v (destroys the database) and start fresh. |
Auto-backup shows last_status=error in Settings |
Backups directory not writable by uid 1000 | Confirm backup_dir_init ran (docker compose ps -a); fix ownership of ABRQ_BACKUP_HOST_DIR. |
| UI loads but API calls fail in the browser | ABRQ_CORS_ALLOWED_ORIGINS does not exactly match the origin in the address bar |
Set the exact scheme, host, and port your users hit. |
Development stack#
For contributors:
docker compose -f docker-compose.dev.yml up -d
Backend API on port 8000 with auto-reload, Vite frontend on port 5173,
PostgreSQL exposed on 5432, Redis on loopback 6379. The postgres init
scripts create the datastore database and a source_demo fixture
database for trying CDC locally. Dev credentials are insecure by design;
never expose this stack beyond your machine.