# Local/dev deployment: API + Postgres. For a single-node SQLite deploy, drop the # `db` service and DATABASE_URL override — the image defaults to SQLite on the # handler-data volume. services: api: image: ghcr.io/0xwheatyz/handler:latest build: . ports: - "8000:8000" environment: DATABASE_URL: postgresql+psycopg://handler:handler@db:5432/handler # Required — the API refuses to start without it. Set in .env or the shell. AUTH_TOKEN: ${AUTH_TOKEN:?set AUTH_TOKEN in .env or the environment} SHARED_CONTEXT_WRITE_TOKEN: ${SHARED_CONTEXT_WRITE_TOKEN:-} WEBHOOK_URL: ${WEBHOOK_URL:-} UI_ENABLED: ${UI_ENABLED:-true} CORS_ORIGINS: ${CORS_ORIGINS:-} # The API computes new projects' root_dir under this path (shared volume with the # control container, which does the actual cloning). PROJECTS_ROOT: /var/lib/handler/projects # Encrypts git-server tokens/SSH keys at rest; must match the control container. HANDLER_SECRET_KEY: ${HANDLER_SECRET_KEY:-} volumes: - handler-data:/var/lib/handler depends_on: db: condition: service_healthy restart: unless-stopped # Control layer: the `handler` worker. Drains the control-command queue the API enqueues # (spawn/kill/resume/approve/reject/forge-init/poll-ci/sync), fires due schedules, and # sweeps CI on an interval. # Shares the database and the handler-data volume with the API. It waits for the API # (which owns migrations), so RUN_MIGRATIONS is off here to avoid a startup race. Run # one-shot control commands against the same image with, e.g., # `docker compose run --rm control handler list`. Every executable it shells out to — # `git`, `tmux`, `node`+`claude`, `mise`, `forge` — is bundled in the image, so live # agent spawning and the claude web-login flow work with no bring-your-own binaries. control: image: ghcr.io/0xwheatyz/handler/control:latest build: context: . dockerfile: Dockerfile.control environment: DATABASE_URL: postgresql+psycopg://handler:handler@db:5432/handler RUN_MIGRATIONS: "false" PROJECTS_ROOT: /var/lib/handler/projects # Per-project forge credentials are resolved from credential_ref pointers at spawn; # export the referenced vars here when spawning agents from this container. FORGE_VERSION: ${FORGE_VERSION:-} # Decrypts git-server tokens/SSH keys stored by the API; must match the API's key. HANDLER_SECRET_KEY: ${HANDLER_SECRET_KEY:-} volumes: - handler-data:/var/lib/handler depends_on: db: condition: service_healthy api: condition: service_healthy restart: unless-stopped db: image: postgres:16-alpine environment: POSTGRES_USER: handler POSTGRES_PASSWORD: handler POSTGRES_DB: handler volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U handler -d handler"] interval: 5s timeout: 3s retries: 10 restart: unless-stopped volumes: handler-data: postgres-data: