Docker Compose Deployment¶
Overview¶
JustIAM ships with two Docker Compose files for running locally or self-hosting:
| File | Purpose |
|---|---|
docker-compose.yml |
Minimal development setup |
docker-compose.single-tenant.yml |
Full single-tenant deployment with self-managed licensing |
Quick start (single-tenant)¶
1. Create environment file¶
Edit .env and set at minimum:
JWT_SECRET=replace-with-a-long-random-string-at-least-32-chars
VAULT_KEY=replace-with-another-long-random-string-32-chars
2. Review tenant configuration¶
The file dev-tenant.yaml defines your tenant:
tenants:
- slug: localhost
db_url: postgres://idpuser:dev-postgres-password@postgres:5432/justiam?sslmode=disable
jwt_secret: "your-jwt-secret-here"
vault_key: "your-vault-key-here"
admin_initial_password: "Admin1234!"
Warning
Do not commit real secrets to version control. Copy dev-tenant.yaml to dev-tenant.local.yaml (gitignored) and point to it via TENANT_CONFIG_FILE_HOST=./dev-tenant.local.yaml in your .env.
3. Start the stack¶
4. Access¶
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:8080 |
| PostgreSQL | localhost:5433 (mapped from container port 5432) |
5. First login¶
See First Login for creating the initial admin account. If admin_initial_password is set in the tenant config, a local admin account is created automatically on first startup.
Services¶
docker-compose.single-tenant.yml¶
PostgreSQL¶
postgres:
image: postgres:16-alpine
ports:
- "5433:5432"
environment:
POSTGRES_DB: ${POSTGRES_DB:-justiam}
POSTGRES_USER: ${POSTGRES_USER:-idpuser}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dev-postgres-password}
volumes:
- postgres_st_data:/var/lib/postgresql/data
A health check (pg_isready) ensures the backend waits for PostgreSQL before starting.
Backend¶
backend:
build:
context: . # Repo root — needs both backend/ and shared/
dockerfile: backend/Dockerfile
ports:
- "8080:8080"
environment:
PORT: "8080"
TENANT_CONFIG_FILE: /etc/justiam/dev-tenant.yaml
VAULT_KEY: ${VAULT_KEY:-dev-vault-key}
TASKS_DB_DSN: ${TASKS_DB_DSN:-postgres://...}
LICENSE_SELF_MANAGED: "true"
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
volumes:
- ${TENANT_CONFIG_FILE_HOST:-./dev-tenant.yaml}:/etc/justiam/dev-tenant.yaml:ro
Key points:
- Build context is the repo root (not
./backend) because the Dockerfile copies bothbackend/andshared/modules TENANT_CONFIG_FILEenables single-tenant mode via a YAML config instead of Kubernetes secretsLICENSE_SELF_MANAGED=trueenables the license management UI in the tenant admin panel- Tenant config is polled every 5 seconds — changes to
dev-tenant.yamltake effect without restart TASKS_DB_DSNpoints to the same database for simplicity; use a separate DB in production
Frontend¶
The frontend is an Nginx container serving the built React app.
docker-compose.yml (minimal)¶
A simpler compose file for development that uses direct environment variables instead of the YAML tenant config:
| Variable | Required | Description |
|---|---|---|
POSTGRES_PASSWORD |
Yes | PostgreSQL password (no default) |
JWT_SECRET |
Yes | HMAC signing secret (no default) |
DATABASE_URL |
No | Defaults to the internal PostgreSQL service |
Tenant configuration reference¶
The dev-tenant.yaml file supports the following fields per tenant:
| Field | Required | Description |
|---|---|---|
slug |
Yes | Short identifier for the tenant (used in logs, API tokens) |
db_url |
Yes | Full PostgreSQL connection string |
jwt_secret |
Yes | ≥32-char random string for signing portal session JWTs |
vault_key |
No | AES-256 key (hex) for encrypting task secrets at rest |
admin_initial_password |
No | Sets the admin password on first startup only |
worker_agent_token |
No | Token for the task-worker agent |
login_worker_mode |
No | "" / inline (default), shared, or dedicated |
login_worker_url |
No | Base URL of login-worker pod (required for shared/dedicated) |
login_worker_token |
No | Bearer token for backend → login-worker auth |
login_worker_agent_token |
No | Agent token for shared login-worker |
login_worker_max_concurrent |
No | Per-tenant concurrency cap for login scripts |
worker_mode |
No | "" / inline (default) or agent |
Tip
The OIDC issuer is derived from the request Host header. When running locally, it becomes http://localhost:8080. Set OIDC_ISSUER explicitly if your frontend URL differs from the backend URL.
Production considerations¶
Reverse proxy¶
In production, place a reverse proxy (Nginx, Traefik, Caddy) in front of JustIAM to handle TLS termination:
┌───────────────┐
https://idp.example.com│ Reverse Proxy │
───────────────────────▶│ (TLS) │
└───┬───────┬───┘
│ │
:8080 │ │ :3000
┌───────▼──┐ ┌──▼────────┐
│ Backend │ │ Frontend │
└──────────┘ └────────────┘
Set TRUSTED_PROXIES to your proxy's CIDR so X-Forwarded-For is trusted for audit logging and rate limiting.
Persistent storage¶
The postgres_st_data named volume persists database data across container restarts. For production, consider:
- Using an external PostgreSQL instance instead of the containerized one
- Setting up regular database backups
- Configuring PostgreSQL
max_connectionsand memory settings
Vault key¶
The VAULT_KEY encrypts task secrets (API keys, tokens used by scheduled scripts) at rest in PostgreSQL. Generate a strong 32-byte hex key:
Danger
Losing the vault key means losing access to all encrypted task secrets. Back it up securely.
Environment variable reference¶
| Variable | Default | Description |
|---|---|---|
POSTGRES_DB |
justiam |
PostgreSQL database name |
POSTGRES_USER |
idpuser |
PostgreSQL user |
POSTGRES_PASSWORD |
dev-postgres-password |
PostgreSQL password |
JWT_SECRET |
— | HMAC secret for portal JWTs |
VAULT_KEY |
— | AES-256 key for secrets encryption |
TASKS_DB_DSN |
Same as db_url |
River scheduler database |
LICENSE_SELF_MANAGED |
false |
Enable tenant-level license management |
TRUSTED_PROXIES |
— | CIDR list for trusted proxies |
TENANT_CONFIG_FILE_HOST |
./dev-tenant.yaml |
Host path to tenant config file |
PORT |
8080 |
Backend HTTP port |
ENVIRONMENT |
developer |
Environment label |