Provision a Kubernetes cluster, durable database storage, object storage, networking, and secrets before installing Sim. The Helm chart installs application workloads on that infrastructure; it does not provision cloud resources.
There is no official Terraform module or CloudFormation template. Call the chart from your infrastructure as code (IaC), for example with Terraform's helm_release or a Helm pipeline task.
The boundary
The chart bundles PostgreSQL and Redis for evaluation. For production, decide how each stateful dependency is backed up, restored, and made available.
| Component | Chart default | Production |
|---|---|---|
| App, realtime, migrations | Deployed | Deployed by the chart |
| Scheduled jobs (CronJobs) | Deployed | Deployed by the chart |
| PostgreSQL | Deployed in-cluster | Replace with managed Postgres — set externalDatabase.* and postgresql.enabled: false |
| Redis | Deployed in-cluster | Replace with a managed cache, or keep the bundled one — it holds no committed data |
| Object storage | None — local disk | Required. S3, Azure Blob, or GCS. Local disk is lost when a container is recreated and is not shared across replicas |
| Ingress | Off | You install the controller; the chart renders the Ingress |
| TLS certificates | Off | You provision them |
| Remote sandbox | Off | Required for Python, Shell, and imported JavaScript — a provider and an immutable Function image. See Sandboxes |
| Pi execution | Off | A separate image from the Function one, set with E2B_PI_TEMPLATE_ID (a template name, an alias, or an immutable <template>:<build-id> ref) or DAYTONA_PI_SNAPSHOT_ID. Unlike the Function base these are not format-validated, but execution still fails closed when unset |
| PII redaction, Ollama, telemetry collector | Off | Optional, off unless you enable them |
The bundled Postgres uses a 10 Gi ReadWriteOnce volume and is intended for evaluation. Moving to managed Postgres is the single most important production change.
The AWS and GCP example values set networkPolicy.enabled: true, and the chart's default egress allows the bundled pods, DNS, and TCP 443 (minus the cloud metadata CIDRs). Moving Postgres or Redis outside the cluster without opening a path leaves them unreachable — add the rules to networkPolicy.egress, or set networkPolicy.allowExternalEgress: true, in the same change that points the app at the managed instance.
Per-cloud requirements
Secrets
The chart offers three strategies. Pick one before you write any IaC, because it decides what your pipeline has to create.
| Strategy | What your IaC creates | When |
|---|---|---|
Inline (app.env, or --set) | Nothing | Evaluation. Values land in your values file, your shell history, and helm get values |
app.secrets.existingSecret.enabled + .name | A Kubernetes Secret carrying every key the app needs, under the standard key names — it is consumed wholesale via envFrom, so key remapping is not supported | You already manage secrets out of band |
| External Secrets Operator | The operator itself, a ClusterSecretStore or SecretStore named in externalSecrets.secretStoreRef.name, entries in your cloud's secret manager, and externalSecrets.remoteRefs.app.<KEY> mappings | Most production deployments |
Non-inline modes handle missing keys differently:
existingSecretfails quietly. The chart renders no Secret of its own, so a key absent from yours and fromapp.envis simply empty at runtime. A key you also set inapp.envis inlined onto the pod spec, where it shadows the Secret — so the Secret is not always the source of truth.- External Secrets fails loudly. The chart refuses to render when a key is set in
app.envbut not mapped, on the grounds that a silently empty secret is worse than a failed install.
See Security for the full secret inventory, which keys are unrecoverable if lost, and what to back up.
Sizing
| Component | Requests | Limits |
|---|---|---|
| app | 4 Gi / 1000m | 8 Gi / 2000m |
| realtime | 512 Mi / 250m | 1 Gi / 500m |
| PostgreSQL (bundled) | 1 Gi / 500m | 2 Gi (no CPU limit) |
These are chart defaults, not capacity benchmarks. Measure memory, CPU, execution concurrency, and database connections with representative workloads. See Scaling for pool budgets and sizing estimates. A node must fit an app pod plus system overhead.
Calling the chart from Terraform
If you already run Terraform, the chart is the resource to wrap — not something to reimplement. Point repository at the OCI registry and pin version; there is no need to vendor the repo.
resource "helm_release" "sim" {
name = "sim"
namespace = "sim"
create_namespace = true
# Always pin `version`. Without it Terraform resolves the newest published
# chart at apply time, which is how an unplanned apply moves Sim to a new
# release with new migrations.
repository = "oci://ghcr.io/simstudioai/charts"
chart = "sim"
version = "1.11.8"
# Or the classic repository, if your tooling does not speak OCI:
# repository = "https://charts.sim.ai"
# Your own values file. The examples under helm/sim/examples/ carry
# placeholder secrets and are starting points, not deployable as-is.
values = [file("${path.module}/values-aws.yaml")]
# helm provider v3 syntax. On v2 each of these is a set_sensitive { } block.
set_sensitive = [
{ name = "app.env.BETTER_AUTH_SECRET", value = var.better_auth_secret },
{ name = "app.env.ENCRYPTION_KEY", value = var.encryption_key },
{ name = "app.env.API_ENCRYPTION_KEY", value = var.api_encryption_key },
{ name = "app.env.INTERNAL_API_SECRET", value = var.internal_api_secret },
{ name = "app.env.CRON_SECRET", value = var.cron_secret },
# Only when the bundled Postgres is in use. With managed Postgres, set
# externalDatabase.* and postgresql.enabled=false instead.
{ name = "postgresql.auth.password", value = var.postgres_password },
]
}The example values files ship literal placeholders such as your-secure-production-auth-secret-here. That includes postgresql.auth.password. The chart only rejects empty values and its own CHANGE-ME strings, so a deployment that inherits those placeholders installs cleanly with a publicly known session-signing secret and database password. Override every secret, or use External Secrets and set none of them inline.
Pin version above. Empty image tags use the selected chart's appVersion, so a pinned chart also selects that default app version. Explicit image tags or digests override it; pin them separately when you need that control, and keep app, realtime, and migrations aligned. Chart 1.11.8 was published with app version v0.9.17; local checkout metadata can differ. See Upgrades.