Skip to content

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

cp .env.single-tenant.example .env

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

docker compose -f docker-compose.single-tenant.yml up --build

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 both backend/ and shared/ modules
  • TENANT_CONFIG_FILE enables single-tenant mode via a YAML config instead of Kubernetes secrets
  • LICENSE_SELF_MANAGED=true enables the license management UI in the tenant admin panel
  • Tenant config is polled every 5 seconds — changes to dev-tenant.yaml take effect without restart
  • TASKS_DB_DSN points to the same database for simplicity; use a separate DB in production

Frontend

frontend:
  build:
    context: ./frontend
    dockerfile: Dockerfile
  ports:
    - "3000:8080"

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
POSTGRES_PASSWORD=secret JWT_SECRET=my-secret docker compose up --build

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_connections and 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:

openssl rand -hex 32

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