Docker

Quick Start

git clone https://github.com/simstudioai/sim.git && cd sim

cat > .env << EOF
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
CRON_SECRET=$(openssl rand -hex 32)
EOF

docker compose -f docker-compose.prod.yml up -d

Open http://localhost:3000

Production Setup

1. Configure Environment

cat > .env << EOF
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
API_ENCRYPTION_KEY=$(openssl rand -hex 32)
CRON_SECRET=$(openssl rand -hex 32)

# Your public origin. BETTER_AUTH_URL is derived from this automatically.
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com

# Database credentials. DATABASE_URL is composed from these by the compose file.
POSTGRES_USER=postgres
POSTGRES_PASSWORD=$(openssl rand -hex 24)
POSTGRES_DB=simstudio
EOF

Do not set DATABASE_URL or BETTER_AUTH_URL in .envdocker-compose.prod.yml composes both on the service definition, and a value set here is ignored. Change POSTGRES_* and NEXT_PUBLIC_APP_URL instead.

Save ENCRYPTION_KEY and API_ENCRYPTION_KEY somewhere outside this server. ENCRYPTION_KEY encrypts workspace and personal environment variables, stored provider API keys, MCP OAuth credentials, and deployment/chat secrets; API_ENCRYPTION_KEY encrypts user-generated Sim API keys. Neither can be regenerated — a database restore paired with a different key leaves the data it protected permanently unreadable.

The compose file refuses to start if BETTER_AUTH_SECRET, ENCRYPTION_KEY, or INTERNAL_API_SECRET is missing, rather than booting with empty values. CRON_SECRET is treated more gently: without it the cron service prints what to set and exits, leaving the rest of the stack running — so upgrading from a compose file that predates the scheduler still works.

Images track latest unless you pin them. For production, see Upgrades.

2. Start Services

docker compose -f docker-compose.prod.yml up -d

Six services start:

ServicePortPurpose
simstudio3000Main application (8 GB memory limit)
realtime3002WebSocket server (1 GB memory limit)
db5432PostgreSQL 17 with pgvector
redisinternalPub/sub and shared cache — not published to the host
cronRuns the background jobs on a schedule
migrationsApplies schema migrations once, then exits

Confirm the five long-running services are up and that migrations has exited cleanly (it is a one-shot job with no healthcheck):

docker compose -f docker-compose.prod.yml ps

3. Put it behind TLS

Caddy is the least-effort option — it obtains and renews certificates automatically.

sim.yourdomain.com {
    request_body {
        max_size 250MB
    }

    handle /socket.io/* {
        reverse_proxy localhost:3002
    }

    reverse_proxy localhost:3000 {
        flush_interval -1
    }
}

Three things in that config are Sim-specific and easy to get wrong: /socket.io must reach the realtime service on 3002, flush_interval -1 stops Caddy buffering streamed agent output into one delayed block, and max_size has to clear the chat endpoint's 220 MB limit.

For nginx, Traefik, or a cloud load balancer — and for the GKE websocket timeout — see Networking.

Ollama

# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d

# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d

Pull additional models — the service name differs by profile:

# GPU profile
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2

# CPU profile
docker compose -f docker-compose.ollama.yml exec ollama-cpu ollama pull llama3.2

External Ollama

If Ollama runs on your host machine (not in Docker):

# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d

# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d

Inside Docker, localhost refers to the container, not your host. Use host.docker.internal or your host's IP.

Commands

# Did migrations succeed?
docker compose -f docker-compose.prod.yml logs migrations

# Is the scheduler firing?
docker compose -f docker-compose.prod.yml logs -f cron

# Upgrade: bump SIM_VERSION in .env, then
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d

Common Questions

Yes. The cron service runs the same jobs the Helm chart schedules as Kubernetes CronJobs, using the schedules in docker/crontab. It needs CRON_SECRET — without it the service prints what to set and exits, and the rest of the stack keeps running.
Redis backs pub/sub for live Chat task status and table events, plus shared caches. Pub/sub has no fallback that works across processes, so live status would not stream without it. The port is deliberately not published so it cannot collide with a local Redis.
Back up with: docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql. Restore with: docker compose -f docker-compose.prod.yml exec -T db psql -U postgres simstudio < backup.sql. The database data is persisted in a Docker volume named postgres_data.
Yes. The docker-compose.prod.yml uses environment variable defaults: POSTGRES_USER (default: postgres), POSTGRES_PASSWORD (default: postgres), POSTGRES_DB (default: simstudio), and POSTGRES_PORT (default: 5432). Set these in your .env file to override them.

On this page