Run this after a first install, an upgrade, or a restore. These checks cover the core deployment; also verify any optional features you enabled, such as SSO, SCIM, data drains, and remote sandboxes.
sim-setup doctor
Run this first. It is the fastest signal you have, and it catches most bad installs before you open a browser.
npx sim-setup doctorRun it from the directory holding your .env and Compose file, or from a source checkout. It reports five groups:
| Group | What it checks |
|---|---|
| Env files | Every .env the layout requires exists. --fix seeds a missing one from the example plus the values already in apps/sim/.env |
| Schema | Required keys are present, are not still the example placeholder, are valid URLs where they should be, and have a usable shape — ENCRYPTION_KEY is checked as exactly 64 hex characters, the rest as a minimum length. Optional secrets are not shape-checked, so a malformed API_ENCRYPTION_KEY passes here and fails later at encrypt time. --fix generates missing or placeholder secrets |
| Consistency | The same key agrees across files — BETTER_AUTH_SECRET between the app and realtime, and DATABASE_URL between the app and packages/db (a mismatch there means migrations ran against a different database). --fix mirrors the apps/sim/.env value |
| Coherence | Configuration that is individually valid but jointly wrong: a feature flag whose dependency is unset, a storage provider that is half-configured, and a server flag whose NEXT_PUBLIC_ twin disagrees with it |
| Live | Postgres is reachable, pgvector is available, migrations are applied — on a standalone npx sim-setup install the published package carries no migration journal, so it can confirm what has been applied but not that it is current — Redis answers, and the running services respond on their ports. Doctor reads env files, so on the production Compose layout — where those values live inside the containers rather than in .env — the Postgres, migration, and Redis probes are skipped and only the HTTP health checks run |
The check worth knowing about before you hit it: if NEXT_PUBLIC_APP_URL points at sim.ai or any subdomain of it, Sim treats the deployment as Sim's own hosted platform (isHosted=true) and disables self-host overrides. Doctor flags this. Use your own domain.
Two flags matter:
--fixapplies the safe repairs above and re-runs the checks. It never touches anything it cannot repair deterministically.--jsonprints the findings as structured JSON instead of the report, so CI can gate on it. The command exits1when anything failed.
The full command list is in the Docker guide. Doctor reads env files, so it covers Compose installs and source checkouts — it is not a Kubernetes tool. On Helm, use the infrastructure checks below.
Checklist
| # | Do this | Proves | If it fails |
|---|---|---|---|
| 1 | Open your Sim URL and create an account | App, database, TLS, migrations | kubectl logs deploy/sim-app — and check the migrations init container |
| 2 | Sign out and sign back in | Session handling, BETTER_AUTH_SECRET, BETTER_AUTH_URL | URLs must match your real origin exactly |
| 3 | Open a workflow and drag two blocks into the editor | Realtime websocket connection | Browser console for socket errors; see Networking |
| 4 | Open the same workflow in a second browser window and edit | Cross-replica collaboration | With >1 replica this needs Redis |
| 5 | Paste a model API key in settings and run a two-block workflow | Execution engine, credential encryption, outbound network | App logs; check ENCRYPTION_KEY is set and outbound egress is allowed |
| 6 | Upload a small file in Files | File storage end to end | With object storage configured: presigned URL + bucket CORS. On local disk: the upload proxies through the app |
| 7 | Upload a file larger than 50 MB | Multipart upload path (object storage only) | Check app logs for provider part-listing or completion errors |
| 8 | Create a knowledge base and upload a PDF | Document parsing, embeddings, pgvector | Needs an embedding provider — see below |
| 9 | Invite a teammate from workspace settings | Email delivery | App logs for the mailer; see Email |
| 10 | Connect an integration account | OAuth configuration | Redirect URI mismatch → see Integrations & OAuth |
| 11 | Create a workflow with a Schedule trigger set to every minute, deploy it, wait 2 minutes | Background jobs | Check the scheduler's logs — see Background Jobs |
| 12 | Trigger a workflow via the API with an API key | Public API and API-key auth | Check the key was created successfully in settings |
Step 11 is the one most people skip and most often discover broken weeks later. Scheduled workflows and every polling trigger depend on the scheduler, and a wrong or missing CRON_SECRET makes it fail silently from the app's side.
Infrastructure checks
After the checklist, confirm the deployment itself is healthy.
# Everything running?
kubectl get pods -n simstudio
# Migrations completed
kubectl logs -n simstudio deploy/sim-app -c migrations --tail=50
# Health endpoints
curl -fsS https://sim.yourdomain.com/api/health
# Background jobs scheduled
kubectl get cronjobs -n simstudio
# Redis configured (multi-replica deployments) — confirms the variable is set,
# not that Redis answers. Checklist steps 3 and 4 above are the real reachability test.
kubectl exec -n simstudio deploy/sim-app -- printenv REDIS_URL# Docker Compose
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs migrations
curl -fsS http://localhost:3000/api/healthAll six should be present on Compose: simstudio, realtime, db, redis, cron, and a completed migrations.
Reading the failures
Step 1 fails — app will not load. Almost always migrations or database connectivity. Check the migrations init container first; a failed migration deliberately blocks the rollout.
Step 2 fails — login loops or rejects. NEXT_PUBLIC_APP_URL or BETTER_AUTH_URL does not match the origin you are browsing. Both must be the exact public URL, with scheme and no trailing slash.
Step 3 fails — no live updates. The reverse proxy is not passing websocket upgrades, or /socket.io is not routed to the realtime service. If realtime is on a separate hostname, NEXT_PUBLIC_SOCKET_URL must point at it and realtime's ALLOWED_ORIGINS must include the app origin.
Step 4 fails — edits do not sync between windows. With more than one replica, this is Redis. Confirm REDIS_URL is present on both pods.
Step 5 fails — execution errors. Check outbound connectivity to the model provider, then the app logs. If the error is about decrypting a credential, ENCRYPTION_KEY differs from the one that encrypted it.
Step 6 or 7 fails. With object storage configured, a CORS error in the browser console means the bucket policy does not allow your Sim origin or the signed upload headers. If step 7 fails only during completion, check the app logs and verify the server identity can list multipart parts (for S3, s3:ListMultipartUploadParts). On local-disk storage there is no CORS involved — uploads proxy through the app, so look at the app logs and the proxy body-size limit instead.
Step 8 fails — knowledge base upload errors. Knowledge bases need an embedding provider: OpenAI, Azure OpenAI, or Gemini with an API key, or a model on your own Ollama via KB_EMBEDDING_MODEL=ollama/<model> and OLLAMA_URL. If one is configured, check pgvector is installed on the database.
Step 9 fails — no email arrives. With no provider configured the mailer no-ops: it records the recipient, subject, and sender at info and reports success, never the message body. Raise LOG_LEVEL to INFO to see that line — the variable is uppercase-only, and at the production default of ERROR nothing is logged at all.
Step 11 fails — schedule never fires. Read the scheduler's logs (docker compose -f docker-compose.prod.yml logs cron, or kubectl get cronjobs -n simstudio). A 401 there means the app and the scheduler disagree on CRON_SECRET.
After an upgrade
Re-run steps 1, 3, 5, 6, and 11 at minimum. Those cover the app, realtime, execution, storage, and background jobs — the five things a bad upgrade breaks.
After a restore
Run the whole list, and pay special attention to step 5 with an OAuth-backed integration. That is what proves ENCRYPTION_KEY matches the backup. An app that loads and logs in but cannot decrypt credentials looks healthy right up until someone runs a real workflow.