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 -dProduction 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
EOFDo not set DATABASE_URL or BETTER_AUTH_URL in .env — docker-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 -dSix services start:
| Service | Port | Purpose |
|---|---|---|
simstudio | 3000 | Main application (8 GB memory limit) |
realtime | 3002 | WebSocket server (1 GB memory limit) |
db | 5432 | PostgreSQL 17 with pgvector |
redis | internal | Pub/sub and shared cache — not published to the host |
cron | — | Runs the background jobs on a schedule |
migrations | — | Applies 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 ps3. 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 -dPull 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.2External 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 -dInside 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