Start here: sim-setup doctor
On a production or local-development Compose install, or a source checkout, run the built-in checker first. It does not detect the Ollama stack. It catches most failures without you having to guess which subsystem is broken. It reads env files, so it is not a Kubernetes tool — on Helm, skip to the checks below.
npx sim-setup doctor
npx sim-setup doctor --fix # repair what it safely can
npx sim-setup doctor --json # machine-readable, for CIOn a standalone Compose install, run it from the directory holding your .env and Compose file. From a source checkout, run it there — it resolves the per-application env files itself and needs no root .env. It exits 1 if anything failed, so it drops straight into a script. See Verify Your Install for what each group of checks covers.
Database connection failed
# The file that started your install — no bare `docker compose` works, the
# repo ships no default docker-compose.yml.
COMPOSE_FILE=docker-compose.prod.yml
# Check database is running
docker compose -f "$COMPOSE_FILE" ps db
# Test connection
docker compose -f "$COMPOSE_FILE" exec db psql -U postgres -c "SELECT 1"Verify DATABASE_URL format: postgresql://user:pass@host:5432/database
Ollama models not showing
Inside Docker, localhost = the container, not your host machine.
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)A workflow cannot reach a service on your network
Outbound requests to private, reserved, and loopback addresses are blocked by default, so a workflow pointed at your Docker host, a LAN service, or a Kubernetes service name fails with a message naming the blocker — the private or loopback address it resolved to, a blocked port, or must use https:// to a public destination when the URL is plain HTTP — and pointing at the allowlist variables.
Name the destination:
EGRESS_ALLOWED_HOSTS=host.docker.internal,*.svc.cluster.local
EGRESS_ALLOWED_IP_RANGES=10.0.0.0/8What naming a destination does and does not grant — plain HTTP, the blocked-port list, database hosts, cloud metadata, Sim Cloud — is covered once in Security.
Two things this does not cover:
- Inside a container
localhostis the container itself, so it will never reach a service on your host. Usehost.docker.internal(the Compose files map it) and name it above. - URLs harvested from content or from a third-party API response — an image URL, a file imported by URL, an MCP OAuth endpoint on a different origin than the MCP server itself — never reach a private network, allowlist or not. Nor does an HTTP block's
proxyUrl.
LM Studio requests route to Ollama
Sim identifies dynamically discovered LM Studio and vLLM models by their vllm/ prefix. If the endpoint is unavailable and you manually enter the raw LM Studio model identifier, Sim treats that unknown identifier as an Ollama model.
-
Set
VLLM_BASE_URL, andVLLM_API_KEYtoo if you enabled LM Studio's API authentication — without the key, discovery requests omit the bearer token and fail. No Compose file sets either for you —docker-compose.ollama.ymlonly setsOLLAMA_URL— so add them to the.envfile next to your Compose file —simstudiodeclaresenv_file: .envin every shipped file — then recreate the service and confirm it landed:Use the Compose file that started your installation —
docker-compose.prod.ymlfor the documented production setup,docker-compose.local.ymlfor a source-built stack, ordocker-compose.ollama.ymlif you started from the Ollama stack:COMPOSE_FILE=docker-compose.prod.yml # or docker-compose.local.yml, docker-compose.ollama.yml docker compose -f "$COMPOSE_FILE" up -d --force-recreate simstudio docker compose -f "$COMPOSE_FILE" exec simstudio printenv VLLM_BASE_URLAn empty result means the variable never reached the container, and model discovery cannot run.
-
In LM Studio, enable Serve on Local Network and API authentication so the container can connect safely.
-
From Docker on macOS or Windows, use
http://host.docker.internal:1234rather thanlocalhost. On Linux, use the host IP. -
The server root and a URL ending in
/v1are both accepted. -
Reload the workspace and select the discovered
vllm/<model-id>option from the model picker.
WebSocket and realtime not working
- Verify reverse proxy routes
/socket.ioto the realtime service (default port 3002).NEXT_PUBLIC_SOCKET_URLis only needed if realtime is on a separate host. - Verify realtime service is running:
docker compose -f docker-compose.prod.yml ps realtime - Ensure reverse proxy passes WebSocket upgrades (see Docker guide)
502 Bad Gateway
# Check app is running — use the file that started your install
docker compose -f docker-compose.prod.yml ps simstudio
docker compose -f docker-compose.prod.yml logs simstudio
# Common causes: out of memory, database not readyMigration errors
Migrations run in their own migrations service and image — the app image does not contain the migration tooling.
# View migration logs
docker compose -f docker-compose.prod.yml logs migrations
# Re-run them
docker compose -f docker-compose.prod.yml up --force-recreate migrationsOn Kubernetes, migrations are an init container on the app pod:
kubectl logs -n simstudio deploy/sim-app -c migrations --tail=200pgvector not found
Use the correct PostgreSQL image:
image: pgvector/pgvector:pg17 # NOT postgres:17Certificate errors (CERT_HAS_EXPIRED)
Rule out expiry and clock drift first: the endpoint's certificate may genuinely have expired, or the host's clock may have drifted far enough to put a valid certificate outside its window. Check the certificate dates and the host date before changing any trust configuration — no CA bundle fixes either.
That leaves two cases a bundle does address: the endpoint is signed by a private CA — a corporate TLS-inspecting proxy, or an internal service — or it serves an incomplete chain, omitting an intermediate. Adding the private root to NODE_EXTRA_CA_CERTS resolves the first. An incomplete chain is different: Node still fails with UNABLE_TO_GET_ISSUER_CERT because the missing intermediate has to be served by the endpoint, so repair the server's chain rather than expecting a bundle to cover it.
For the private-CA case, the image already ships current CA certificates and runs as a non-root user, so installing packages inside it is not the fix. Mount your CA bundle and point Node at it — an incomplete chain is not fixed here, only on the server:
# docker-compose.prod.yml
services:
simstudio:
volumes:
- /etc/ssl/certs/corporate-ca.crt:/certs/corporate-ca.crt:ro
environment:
- NODE_EXTRA_CA_CERTS=/certs/corporate-ca.crt# Helm — mount a ConfigMap holding the CA
app:
env:
NODE_EXTRA_CA_CERTS: /certs/corporate-ca.crt
extraVolumes:
- name: corporate-ca
configMap:
name: corporate-ca
extraVolumeMounts:
- name: corporate-ca
mountPath: /certs
readOnly: trueNODE_TLS_REJECT_UNAUTHORIZED=0 disables certificate verification entirely and should never be used outside a throwaway test.
Blank page after login
- Check browser console for errors
- Verify
NEXT_PUBLIC_APP_URLmatches your actual domain - Clear browser cookies and local storage
- Check that all services are running:
docker compose -f docker-compose.prod.yml ps
Windows-specific issues
These apply to running Sim from source for development, not to the Docker or Kubernetes deployments, which are unaffected by the host OS.
Turbopack errors on Windows: use WSL2.
wsl --installLine ending issues:
# Configure git to use LF
git config --global core.autocrlf inputScheduled workflows never run
The most common self-hosting surprise.
Docker Compose — check the cron service is running and read its logs:
docker compose -f docker-compose.prod.yml logs --tail=50 cronA 401 there means the app and the scheduler disagree on CRON_SECRET.
Kubernetes — check the CronJobs are present and firing:
kubectl get cronjobs -n simstudio
kubectl get jobs -n simstudio --sort-by=.metadata.creationTimestamp | tailA stale LAST SCHEDULE or failing jobs usually means CRON_SECRET is missing or does not match between the cron pods and the app. Call the endpoint by hand to see the status code:
kubectl exec -n simstudio deploy/sim-app -- sh -c \
'curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $CRON_SECRET" \
http://localhost:3000/api/schedules/execute'Wrap it in sh -c with single quotes so $CRON_SECRET expands inside the pod — otherwise your local shell substitutes an empty value and you get a misleading 401.
401 means the secret does not match. 202 means the endpoint accepted the run; it does not tell you whether a schedule was actually due, so confirm in the Logs view.
Gmail, Drive, and Outlook triggers never fire
These are polling triggers, driven by the per-minute /api/webhooks/poll/* jobs. Check the scheduler is running them — docker compose -f docker-compose.prod.yml logs cron, or kubectl get cronjobs -n simstudio for a recent LAST SCHEDULE.
Microsoft Teams chat triggers are the different case: they use a Microsoft Graph subscription capped at about three days, renewed by the twice-daily renew-subscriptions job. If Teams triggers work for a couple of days and then stop, that job is not running. See Background Jobs.
Collaboration breaks with multiple replicas
Two users editing the same workflow stop seeing each other, or live status never updates — with no error anywhere.
This is Redis. Pub/sub and the Socket.IO adapter have no cross-pod fallback:
kubectl exec -n simstudio deploy/sim-app -- printenv REDIS_URL
kubectl exec -n simstudio deploy/sim-realtime -- printenv REDIS_URLBoth pods must have REDIS_URL. On Helm they share one Secret, so setting it under app.env covers both. See Redis.
App crashes at startup with a REDIS_TLS_SERVERNAME error
REDIS_URL uses rediss:// pointed at a bare IP address. TLS certificates cannot be verified against an IP, so set REDIS_TLS_SERVERNAME to the DNS name the certificate was issued for — or use a DNS hostname in the URL instead.
File uploads fail with a CORS error
The bucket's CORS policy does not allow your Sim origin. Uploads go directly from the browser to object storage via presigned PUT, so server-side configuration being correct is not enough.
If small uploads succeed but files over 50 MB fail during completion, check the app logs for the provider's part-listing request. The server completes multipart uploads from provider-authoritative state; for S3, its identity needs s3:ListMultipartUploadParts. See Object Storage.
Agent output arrives all at once
Your reverse proxy is buffering the response stream. Set proxy_buffering off (Nginx) or flush_interval -1 (Caddy). See Networking.
Websockets disconnect every 30 seconds
The load balancer's backend timeout is closing them. On GKE, attach a BackendConfig with timeoutSec: 3600 to the realtime Service; on AWS, raise the ALB idle_timeout. Clients reconnect, so this degrades rather than breaks. See Networking.
Knowledge base upload fails
Embeddings need a provider — set OPENAI_API_KEY, configure Azure OpenAI, set KB_EMBEDDING_MODEL=gemini-embedding-001 with a Gemini key, or set KB_EMBEDDING_MODEL=ollama/<model> with OLLAMA_URL to embed on your own Ollama. On a self-hosted deployment OPENROUTER_API_KEY also serves the OpenAI-family models on its own, as a fallback behind any OpenAI or Azure credentials you have. If one is configured, verify pgvector is installed on the database.
A document that fails with vector 0 has N unexpected dimensions means EMBEDDING_OUTPUT_DIMS does not match what the model actually emits. The message names both widths. If the width the model returned is one of 384, 768, 1024, 1536, or 3072, set the variable to it and recreate the knowledge base. If it is anything else, no column can store it — choose a model that emits one of those five instead, since setting an unstorable width silently falls back to 1536 and the next document fails the same way. Existing knowledge bases keep the width they were created with.
Credentials Unreadable After a Restore
Integrations show as connected but fail, or provider keys error on decrypt. ENCRYPTION_KEY does not match the value in use when the backup was taken. There is no recovery — the original key must be restored.
Kubernetes: App Pods Never Become Ready
Check the migrations init container first — a failed migration deliberately blocks the rollout:
kubectl logs -n simstudio deploy/sim-app -c migrations --tail=200
kubectl describe pod -n simstudio <pod>Common causes: DATABASE_URL unreachable, the database user lacking rights to create the vector extension, or an OOMKill from insufficient memory. See Upgrades for migration-failure recovery.
Kubernetes: ImagePullBackOff
Either the tag does not exist in the registry (helm get values sim and check), or you are pulling from a private registry without global.imagePullSecrets. When mirroring into a private registry, set global.useRegistryForAllImages: true — otherwise third-party images still point at Docker Hub.
Kubernetes: Postgres Pod Pending
kubectl describe pvc -n simstudioAlmost always no default StorageClass, no PV provisioner installed, or a StorageClass that does not support ReadWriteOnce. Set global.storageClass to pick a specific one.