Skip to content

Docker Compose#

Single-node deployment for dev, laptop demos, and small PoCs. This is the mode the Quickstart uses.

What you get#

  • api container — FastAPI backend + agent runtime
  • dashboard container — Next.js UI
  • db container — Postgres 16 (or SQLite if you strip this)
  • redis container — cache (optional; used only for LLM response caching + rate-limiting)
  • Local volume mounts for pipeline_runs/, ml_team/data/, ~/.swarm/

Full docker-compose.yml#

version: '3.9'

services:
  api:
    image: ghcr.io/theaisingularity/swarm-api:0.11.0
    restart: unless-stopped
    ports:
      - "8000:8000"
    env_file:
      - .env
    environment:
      SWARM_DB_URL: postgresql://swarm:swarm@db:5432/swarm
      SWARM_API_HOST: 0.0.0.0
      SWARM_API_PORT: 8000
    volumes:
      - ./ml_team/data:/app/ml_team/data
      - ./pipeline_runs:/app/pipeline_runs
      - swarm-state:/root/.swarm
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/healthz"]
      interval: 30s
      timeout: 5s
      retries: 3

  dashboard:
    image: ghcr.io/theaisingularity/swarm-dashboard:0.11.0
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NEXT_PUBLIC_API_URL: http://localhost:8000
    depends_on:
      - api

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: swarm
      POSTGRES_PASSWORD: swarm
      POSTGRES_DB: swarm
    volumes:
      - swarm-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U swarm"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    ports:
      - "6379:6379"

volumes:
  swarm-db:
  swarm-state:

File at deploy/compose/docker-compose.yml.

.env template#

# Authentication
SWARM_JWT_SECRET=<generate-a-long-random-string>

# LLM providers
ANTHROPIC_API_KEY=sk-ant-...
# or
OPENAI_API_KEY=sk-proj-...

# Logging
SWARM_LOG_LEVEL=INFO

First boot#

cp .env.example .env                    # edit with your keys
docker compose up -d
docker compose logs -f api              # watch startup; grep for "SEEDED ADMIN" to get password

Common operations#

Restart one service#

docker compose restart api

Upgrade#

docker compose pull                     # pull latest images
docker compose up -d                    # recreate with new images
# verify
curl http://localhost:8000/healthz

Backup#

docker compose exec db pg_dump -U swarm swarm > backup-$(date +%F).sql

Restore#

docker compose exec -T db psql -U swarm swarm < backup-2026-04-15.sql

Reset everything#

docker compose down -v                  # -v removes volumes

Resource sizing#

For dev/demo (single user, 1-2 pipelines at a time):

  • Docker Desktop → Settings → Resources → 6 CPUs, 12 GB RAM, 40 GB disk
  • Linux: no hard limits needed; fits on 8-core / 16-GB laptop

For a small team (3-5 users, 10 pipelines/day):

  • Move Postgres to managed (Neon / Supabase free tier) — see SWARM_DB_URL
  • Increase SWARM_API_WORKERS to 4
  • Move object storage to S3 / GCS (SWARM_STORAGE_BACKEND=s3)

At that point you're on the path to Kubernetes. Proceed to Kubernetes + Helm.

When NOT to use Docker Compose#

  • > 10 concurrent pipelines — single-replica API bottlenecks
  • Enterprise procurement — customers want Kubernetes artefacts for security review
  • Multi-tenant SaaS — Compose has no namespace isolation
  • HA / 99.95% SLA — single container = single point of failure

Using Compose for a proper PoC#

If a prospect wants a self-hosted PoC but isn't ready for K8s, Compose + a reverse proxy + managed Postgres is fine:

# Add nginx / Caddy in front of api + dashboard
# for TLS + custom domain:
services:
  proxy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
# Caddyfile
swarm.customer.com {
    reverse_proxy dashboard:3000
}

api.swarm.customer.com {
    reverse_proxy api:8000
}

Auto-TLS via Let's Encrypt. Still single-node, but customer-accessible.

Troubleshooting#

Port 8000 already in use

lsof -i :8000 to find the other process. Remap: ports: - "18000:8000" and update NEXT_PUBLIC_API_URL.

Postgres hangs at startup

Delete the volume: docker volume rm swarm_swarm-db. Data loss — make sure you've backed up.

Dashboard can't reach API

Different hosts from container's perspective. In docker-compose, NEXT_PUBLIC_API_URL should use the service name (http://api:8000) for server-to-server, http://localhost:8000 for browser. Default config handles this via reverse proxy logic in the dashboard.

Next#