Deploy Sim on a Kubernetes cluster you operate. The chart bundles PostgreSQL with pgvector and Redis, runs migrations before the app starts, and enables scheduled jobs by default.
The examples pin published chart 1.11.8, whose published app version is v0.9.17. This is a pinned example, not a claim about the newest release. Inspect the chart values or run helm show values oci://ghcr.io/simstudioai/charts/sim --version 1.11.8 for its configuration.
Prerequisites
- A maintained Kubernetes cluster. The chart's declared compatibility minimum is 1.25.
- Helm 3.8 or later for OCI installation.
- A default StorageClass supporting
ReadWriteOncefor bundled PostgreSQL. - An installed ingress controller if you enable ingress, plus a certificate for public TLS.
metrics-serverif you enable autoscaling.- External Secrets Operator and a configured SecretStore/ClusterSecretStore if you choose that secret strategy.
Redis is bundled by default. A non-empty app.env.REDIS_URL overrides its URL and suppresses the bundled Redis Deployment. redis.enabled=false is the explicit opt-out; supply an external URL if you need cross-replica events. See Redis.
Installation
1. Choose the secret strategy
| Strategy | Configuration |
|---|---|
| Inline values | The chart writes app.env/realtime.env into a shared Kubernetes Secret. Values are also stored in Helm release data; do not commit credentials to Git. |
| Existing Secret | Set app.secrets.existingSecret.enabled=true and its name. Prepare every required key using the standard env names. There is no key remapping. |
| External Secrets Operator (ESO) | Set externalSecrets.enabled=true and map keys under externalSecrets.remoteRefs.app. ESO supplies the Secret; the chart does not create its inline app Secret. |
In existing-Secret mode, non-empty app.env and realtime.env values are inlined into pod specs. Keep secret material in the pre-created Secret. In ESO mode, every non-empty key under those env maps must also have a remote reference; the chart rejects missing mappings. Put non-secret URL/configuration values in envDefaults instead.
2. Prepare new-install secrets
The following installation uses inline values to demonstrate a local release. For an existing release, recover and retain its current values rather than generating replacements. For a secret-store installation, store the same required values there and use the secret-store examples.
CHART_VERSION=1.11.8
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)
POSTGRES_PASSWORD=$(openssl rand -hex 24)Save the secrets durably before installing. ENCRYPTION_KEY and API_ENCRYPTION_KEY must be 64-character hex strings. Losing a key makes the values encrypted with it unreadable. Without API_ENCRYPTION_KEY, legacy Sim API keys can be stored in plain text; the app logs a warning when its key getter is called.
CRON_SECRET is required with the default cronjobs.enabled=true. The chart rejects an absent inline value. With an existing Secret, the key must be present there; with ESO, it must be mapped. Set cronjobs.enabled=false only if you intend to run no chart-scheduled jobs.
3. Install the pinned chart
helm install sim oci://ghcr.io/simstudioai/charts/sim \
--version "$CHART_VERSION" \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.API_ENCRYPTION_KEY="$API_ENCRYPTION_KEY" \
--set app.env.CRON_SECRET="$CRON_SECRET" \
--set postgresql.auth.password="$POSTGRES_PASSWORD" \
--namespace simstudio --create-namespaceIngress is disabled by default. For local access, forward the app and realtime ports in separate terminals:
kubectl port-forward deployment/sim-app 3000:3000 -n simstudiokubectl port-forward deployment/sim-realtime 3002:3002 -n simstudioOpen http://localhost:3000. For public access, complete the topology below and apply it with the same secrets or secret-store configuration.
Public access
Configure a public app origin, realtime routing, allowed origins, and TLS together. This same-host example belongs in public-values.yaml; replace the ingress class with your installed controller's class:
app:
image:
tag: "v0.9.17"
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
BETTER_AUTH_URL: "https://sim.yourdomain.com"
NEXT_PUBLIC_SOCKET_URL: "https://sim.yourdomain.com"
realtime:
image:
tag: "v0.9.17"
env:
ALLOWED_ORIGINS: "https://sim.yourdomain.com"
migrations:
image:
tag: "v0.9.17"
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: "<your-ingress-class>"
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: null
app:
host: sim.yourdomain.com
realtime:
host: sim.yourdomain.com
paths:
- path: /socket.io
pathType: Prefix
tls:
enabled: true
secretName: sim-tlsThe app and realtime consume shared app env values; ALLOWED_ORIGINS explicitly admits the public app origin. Matching ingress hosts lets the chart route /socket.io before the app's / path. Keep app, realtime, and migrations on the same image release.
Create sim-tls in simstudio from your certificate and private key, or configure your certificate manager to populate that Secret:
kubectl create secret tls sim-tls -n simstudio \
--cert=fullchain.pem --key=privkey.pemFor a fresh public installation, create the namespace before creating the Secret. Configure DNS and the controller's own HTTPS redirect, upload-size, WebSocket, timeout, and streaming settings. The null above removes the chart's legacy nginx redirect annotation; it does not configure another controller.
For a separate realtime hostname, change all three fields together:
app:
env:
NEXT_PUBLIC_SOCKET_URL: "https://sim-ws.yourdomain.com"
ingress:
realtime:
host: sim-ws.yourdomain.com
paths:
- path: /
pathType: PrefixKeep ALLOWED_ORIGINS set to the app origin, and use a TLS certificate covering both hosts. App/auth URLs remain on sim.yourdomain.com.
Apply your complete values with helm upgrade --install, retaining the same secrets as installation. For inline mode, repeat the secret --set arguments from the install command and add -f public-values.yaml. For secret-store mode, use a complete values file defining that strategy.
Ingress and TLS
The chart defaults still name nginx and its annotations. Kubernetes ingress-nginx retired in March 2026 and no longer receives security fixes. Use a maintained controller supported by your cluster; the chart renders Ingress objects and does not provision a controller or Gateway API resources. See the Kubernetes retirement announcement.
For an existing legacy ingress-nginx deployment, these settings describe the required body-size and streaming behavior:
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "250m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"These annotations do not apply to other controllers. On GKE, configure a BackendConfig timeout for long-lived WebSockets and create the ManagedCertificate referenced by the values. See Networking.
Secret stores
Existing Secret
Prepare an app Secret containing at least BETTER_AUTH_SECRET, ENCRYPTION_KEY, INTERNAL_API_SECRET, API_ENCRYPTION_KEY, and CRON_SECRET, plus any provider credentials you use. For bundled PostgreSQL, prepare its password Secret too:
app:
secrets:
existingSecret:
enabled: true
name: sim-app-secrets
postgresql:
auth:
existingSecret:
enabled: true
name: sim-postgresql-secret
passwordKey: POSTGRES_PASSWORDUse the public URL configuration above without putting credentials under app.env. Both Deployments consume the app Secret. See the complete existing-Secret example.
External Secrets Operator
Populate the remote keys and create the referenced store before installing. For a same-host public installation, move URL values from app.env and realtime.env into these envDefaults maps, then add the ingress/TLS configuration above:
app:
envDefaults:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
BETTER_AUTH_URL: "https://sim.yourdomain.com"
NEXT_PUBLIC_SOCKET_URL: "https://sim.yourdomain.com"
realtime:
envDefaults:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
BETTER_AUTH_URL: "https://sim.yourdomain.com"
ALLOWED_ORIGINS: "https://sim.yourdomain.com"
externalSecrets:
enabled: true
apiVersion: "v1"
secretStoreRef:
name: sim-secret-store
kind: ClusterSecretStore
remoteRefs:
app:
BETTER_AUTH_SECRET: "sim/app/better-auth-secret"
ENCRYPTION_KEY: "sim/app/encryption-key"
INTERNAL_API_SECRET: "sim/app/internal-api-secret"
API_ENCRYPTION_KEY: "sim/app/api-encryption-key"
CRON_SECRET: "sim/app/cron-secret"
postgresql:
password: "sim/postgresql/password"Map additional credentials, such as RESEND_API_KEY, under remoteRefs.app. Remote value changes do not themselves restart processes; recreate the affected pods after credentials change. See the complete ESO example.
Configure model provider keys, such as OPENAI_API_KEY, through the same selected secret strategy. A healthy release does not establish that a model provider is configured.
Cloud-specific values
Use a cloud example as a starting file, not a complete install configuration. Fetch it from the release corresponding to the pinned chart example:
SIM_RELEASE=v0.9.17
curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml"Choose values-aws.yaml for EKS, values-azure.yaml for AKS, or values-gcp.yaml for GKE. Review and replace provider-specific fields before installation:
| Example | Required review |
|---|---|
| AWS | StorageClass, region/buckets, IAM role ARN, ACM certificate ARN, ALB controller, and network policy. |
| Azure | StorageClass, Blob account/containers/credentials, certificate issuer, and legacy ingress-nginx settings. |
| GCP | Project/buckets, Workload Identity, static IP, ManagedCertificate, backend timeout, and network policy. |
Each file also has six placeholder public-address values. In inline mode, reuse your saved secrets and override those addresses together:
helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \
--version 1.11.8 \
--values values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.API_ENCRYPTION_KEY="$API_ENCRYPTION_KEY" \
--set app.env.CRON_SECRET="$CRON_SECRET" \
--set postgresql.auth.password="$POSTGRES_PASSWORD" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--set app.env.BETTER_AUTH_URL="https://sim.yourdomain.com" \
--set app.env.NEXT_PUBLIC_SOCKET_URL="https://sim-ws.yourdomain.com" \
--set realtime.env.ALLOWED_ORIGINS="https://sim.yourdomain.com" \
--set ingress.app.host="sim.yourdomain.com" \
--set ingress.realtime.host="sim-ws.yourdomain.com" \
--namespace simstudio --create-namespaceReplace the file name for another cloud and keep your reviewed file in your configuration repository. With ESO, use its env/default and remote-reference rules rather than the inline-secret arguments.
When converting an existing generic release, retain its original encryption secrets and database name. The generic chart initializes database sim; cloud examples use simstudio, and changing the value does not rename an existing database. Add --set postgresql.auth.database=sim when retaining that database. helm uninstall does not delete StatefulSet PVCs; deleting them to start fresh erases the stored data.
For an inline installation, helm get values sim -n simstudio can recover saved values. For external secret modes, use the existing secret store. Do not generate new encryption keys during conversion.
Verify the release
kubectl get pods -n simstudio
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
kubectl get cronjobs -n simstudio
curl -fsS https://sim.yourdomain.com/api/healthScheduled jobs cover workflows, polling triggers, connector syncs, token cleanup, data drains, and outbox processing. Inspect failed job pods and endpoint responses, then run Verify your install.
Verify the chart
Published charts have Sigstore keyless signatures and build-provenance attestations in the source registry. Use cosign 3.0 or later for the bundle format; 3.1+ auto-detects old and new formats:
cosign verify ghcr.io/simstudioai/charts/sim:1.11.8 \
--certificate-identity-regexp '^https://github.com/simstudioai/sim/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.11.8 --repo simstudioai/simThere is no GPG .prov file; helm install --verify does not verify these signatures. When mirroring charts, copy the associated signature and attestation artifacts as well as the chart. A chart-only copy does not preserve verification material.
External database
Configure a database with pgvector and the required migration permissions. The chart constructs the shared app/realtime/migration DSN from:
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"Replace the credentials or supply the chart's external-database Secret strategy. Inline passwords must use letters, digits, ., _, or - to pass chart DSN validation. See Cloud platforms and Upgrades for extensions and direct migration connections.
Helm repository
For tooling that requires a classic repository:
helm repo add sim https://charts.sim.ai
helm repo updateUse sim/sim in place of the OCI URL in the install/upgrade command, preserving the version, complete values, namespace, and secret configuration. Signature/provenance verification above applies to the OCI artifact.
Operator commands
Upgrade with the full configuration and an explicitly chosen chart version:
helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \
--version 1.11.8 --namespace simstudio --values values.yamlRetain the original secrets. See Upgrades before changing the target version.
Uninstall removes the release but leaves PostgreSQL StatefulSet PVCs:
helm uninstall sim --namespace simstudio