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 when pinned, then
bun run sim update

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