|
| 1 | +--- |
| 2 | +title: Air-Gapped Deployments |
| 3 | +description: Running Sim with a private registry and restricted or no outbound internet access |
| 4 | +--- |
| 5 | + |
| 6 | +import { Callout } from 'fumadocs-ui/components/callout' |
| 7 | +import { Step, Steps } from 'fumadocs-ui/components/steps' |
| 8 | +import { FAQ } from '@/components/ui/faq' |
| 9 | + |
| 10 | +Sim runs in restricted-egress and fully air-gapped environments, with real functional limits. Read [what does not work](#what-does-not-work-without-internet-access) before committing to an air-gapped deployment. |
| 11 | + |
| 12 | +## Mirroring images |
| 13 | + |
| 14 | +Every image Sim uses: |
| 15 | + |
| 16 | +| Image | Purpose | |
| 17 | +|---|---| |
| 18 | +| `ghcr.io/simstudioai/simstudio` | App | |
| 19 | +| `ghcr.io/simstudioai/realtime` | Realtime | |
| 20 | +| `ghcr.io/simstudioai/migrations` | Migrations | |
| 21 | +| `pgvector/pgvector:pg17` | PostgreSQL (if not using managed) | |
| 22 | +| `redis:7-alpine` | Redis — bundled by both Compose and the chart | |
| 23 | +| `ghcr.io/simstudioai/cron` | Scheduler (Docker Compose) | |
| 24 | +| `curlimages/curl:8.5.0` | CronJob runner (Kubernetes) | |
| 25 | +| `ghcr.io/simstudioai/pii` | PII redaction (optional) | |
| 26 | +| `ollama/ollama` | Local inference (optional) | |
| 27 | +| `otel/opentelemetry-collector-contrib` | Telemetry collector (optional) | |
| 28 | +| `busybox:1.36` | `helm test` connectivity probe (only needed if you run `helm test`) | |
| 29 | + |
| 30 | +If you enable the Copilot service, also mirror `ghcr.io/simstudioai/copilot` and the `postgres` image its StatefulSet uses. |
| 31 | + |
| 32 | +Mirror them at a pinned tag: |
| 33 | + |
| 34 | +```bash |
| 35 | +VERSION=v0.7.44 |
| 36 | +REGISTRY=registry.internal.acme.com |
| 37 | + |
| 38 | +for img in simstudio realtime migrations cron; do |
| 39 | + docker pull "ghcr.io/simstudioai/$img:$VERSION" |
| 40 | + docker tag "ghcr.io/simstudioai/$img:$VERSION" "$REGISTRY/simstudioai/$img:$VERSION" |
| 41 | + docker push "$REGISTRY/simstudioai/$img:$VERSION" |
| 42 | +done |
| 43 | + |
| 44 | +for img in "pgvector/pgvector:pg17" "redis:7-alpine" "curlimages/curl:8.5.0"; do |
| 45 | + docker pull "$img" |
| 46 | + docker tag "$img" "$REGISTRY/$img" |
| 47 | + docker push "$REGISTRY/$img" |
| 48 | +done |
| 49 | +``` |
| 50 | + |
| 51 | +For a truly disconnected network, `docker save` the images to a tarball, transfer it on approved media, and `docker load` on the other side. |
| 52 | + |
| 53 | +## Pointing the chart at your registry |
| 54 | + |
| 55 | +```yaml |
| 56 | +global: |
| 57 | + imageRegistry: "registry.internal.acme.com" |
| 58 | + # Route ALL images through your registry, not just simstudioai/* ones |
| 59 | + useRegistryForAllImages: true |
| 60 | + imagePullSecrets: |
| 61 | + - name: internal-registry-creds |
| 62 | + |
| 63 | +app: |
| 64 | + image: |
| 65 | + tag: "v0.7.44" |
| 66 | +realtime: |
| 67 | + image: |
| 68 | + tag: "v0.7.44" |
| 69 | +migrations: |
| 70 | + image: |
| 71 | + tag: "v0.7.44" |
| 72 | +``` |
| 73 | +
|
| 74 | +<Callout type="warn"> |
| 75 | + `useRegistryForAllImages` defaults to `false`, which routes only `simstudioai/*` images through your registry and leaves the third-party ones — `redis`, `pgvector/pgvector`, `curlimages/curl`, `ollama/ollama`, `otel/opentelemetry-collector-contrib`, `busybox`, and `postgres` (Copilot only) — pointing at Docker Hub. In an air-gapped environment that produces `ImagePullBackOff` on components you did not think to mirror. Set it to `true`. |
| 76 | +</Callout> |
| 77 | + |
| 78 | +Create the pull secret: |
| 79 | + |
| 80 | +```bash |
| 81 | +kubectl create secret docker-registry internal-registry-creds \ |
| 82 | + --namespace simstudio \ |
| 83 | + --docker-server=registry.internal.acme.com \ |
| 84 | + --docker-username=<user> \ |
| 85 | + --docker-password=<password> |
| 86 | +``` |
| 87 | + |
| 88 | +The chart itself is a directory in the repository — clone it, or package it with `helm package` and transfer the `.tgz`. |
| 89 | + |
| 90 | +## Egress allowlist |
| 91 | + |
| 92 | +If you have restricted rather than zero egress, allow these destinations from the app. |
| 93 | + |
| 94 | +**Required for the deployment itself** — nothing. The app, realtime, and migrations components have no license check and no activation step. (The optional self-hosted Copilot service is the exception: the chart requires a Sim-issued `copilot.server.env.LICENSE_KEY` when you enable it.) |
| 95 | + |
| 96 | +<Callout type="warn"> |
| 97 | + Sim does attempt one outbound call you did not ask for: **anonymous telemetry** to `https://telemetry.simstudio.ai/v1/traces`, on by default. In a restricted-egress deployment it simply fails, but set `NEXT_TELEMETRY_DISABLED=1` to stop the attempts — or point `TELEMETRY_ENDPOINT` at an internal collector. See [Observability](/platform/self-hosting/observability). |
| 98 | +</Callout> |
| 99 | + |
| 100 | +**Required for the features you use:** |
| 101 | + |
| 102 | +| Destination | Needed for | |
| 103 | +|---|---| |
| 104 | +| `api.openai.com` | OpenAI models and the default knowledge base embeddings | |
| 105 | +| `api.anthropic.com` | Anthropic models | |
| 106 | +| `generativelanguage.googleapis.com` | Gemini models and `gemini-embedding-001` embeddings | |
| 107 | +| `*.openai.azure.com` | Azure OpenAI | |
| 108 | +| Your object storage endpoint | File storage (server side) | |
| 109 | +| Your email provider | Invitations, verification | |
| 110 | +| Provider OAuth + API endpoints | Each integration you enable | |
| 111 | +| `www.copilot.sim.ai` | The Chat module (`COPILOT_API_KEY`); override with `SIM_AGENT_API_URL` | |
| 112 | +| `*.e2b.app` / Daytona endpoint | Remote code sandboxes, if enabled. The E2B control-plane domain is overridable with `E2B_DOMAIN` | |
| 113 | + |
| 114 | +When object storage is configured, the **browser** also needs to reach its endpoint directly, because uploads are presigned `PUT`s sent from the client. If your users are on the restricted network too, that endpoint must be reachable from their machines. |
| 115 | + |
| 116 | +<Callout type="error"> |
| 117 | + There is no global forward-proxy setting. Sim does not read `HTTP_PROXY` / `HTTPS_PROXY`, so model-provider calls, integration calls, and email delivery cannot be routed through a forward proxy. (The HTTP Request block accepts a per-request `proxyUrl`, but that covers only that one block, not the platform's own outbound traffic.) Environments with a mandatory egress proxy need a transparent proxy or NAT-based egress instead. |
| 118 | +</Callout> |
| 119 | + |
| 120 | +## What does not work without internet access |
| 121 | + |
| 122 | +<Callout type="warn"> |
| 123 | + Plan around these before committing. None of them have a local workaround today. |
| 124 | +</Callout> |
| 125 | + |
| 126 | +| Feature | Why | Workaround | |
| 127 | +|---|---|---| |
| 128 | +| **Knowledge bases** | Embeddings are generated by a hosted provider — OpenAI, Azure OpenAI, or Gemini. There is no local embedding backend. | Allow egress to one of those endpoints, or do not use knowledge bases | |
| 129 | +| **Chat module** | Requires `COPILOT_API_KEY` and reaches `www.copilot.sim.ai` | Set `NEXT_PUBLIC_CHAT_DISABLED=true` to hide it | |
| 130 | +| **Hosted model providers** | Reach the provider's API | Run Ollama, vLLM, or LiteLLM inside the network | |
| 131 | +| **All integrations** | Reach the third-party service | Unavoidable — the service is external by definition | |
| 132 | +| **Webhook triggers** | The provider must reach your instance | Use polling triggers if the provider is reachable outbound | |
| 133 | +| **Remote sandboxes (E2B / Daytona)** | External services | The in-process isolated-vm sandbox still runs JavaScript, but it is not a full substitute — features that depend on a remote sandbox image (Python execution, shell tooling, document generation) are unavailable | |
| 134 | + |
| 135 | +### Local model inference |
| 136 | + |
| 137 | +The one substitution that works cleanly. Any OpenAI-compatible endpoint inside your network: |
| 138 | + |
| 139 | +```bash |
| 140 | +# Ollama |
| 141 | +OLLAMA_URL=http://ollama.internal:11434 |
| 142 | +
|
| 143 | +# vLLM |
| 144 | +VLLM_BASE_URL=http://vllm.internal:8000 |
| 145 | +VLLM_API_KEY=<optional> |
| 146 | +
|
| 147 | +# LiteLLM proxy |
| 148 | +LITELLM_BASE_URL=http://litellm.internal:4000 |
| 149 | +LITELLM_API_KEY=<optional> |
| 150 | +``` |
| 151 | + |
| 152 | +The chart can deploy Ollama in-cluster, with optional GPU support: |
| 153 | + |
| 154 | +```yaml |
| 155 | +ollama: |
| 156 | + enabled: true |
| 157 | + persistence: |
| 158 | + size: 100Gi |
| 159 | +``` |
| 160 | + |
| 161 | +Models must be pulled into the volume from a connected machine and transferred, or pulled during a connected bootstrap window. |
| 162 | + |
| 163 | +<Callout type="info"> |
| 164 | + Local inference does **not** solve knowledge bases. Embeddings go to whichever hosted embedding provider `KB_EMBEDDING_MODEL` selects — OpenAI, Azure OpenAI, or Gemini — never through the configured chat model, so an Ollama deployment still cannot generate knowledge base embeddings. |
| 165 | +</Callout> |
| 166 | + |
| 167 | +## Deployment sequence |
| 168 | + |
| 169 | +<Steps> |
| 170 | + |
| 171 | +<Step> |
| 172 | + |
| 173 | +### Mirror and verify images |
| 174 | + |
| 175 | +Confirm each mirrored image pulls from inside the network before deploying anything. |
| 176 | + |
| 177 | +```bash |
| 178 | +crane manifest registry.internal.acme.com/simstudioai/simstudio:v0.7.44 |
| 179 | +``` |
| 180 | + |
| 181 | +</Step> |
| 182 | + |
| 183 | +<Step> |
| 184 | + |
| 185 | +### Provision infrastructure |
| 186 | + |
| 187 | +PostgreSQL with pgvector, Redis, object storage (MinIO or Ceph RGW work well on-premises — see [Object Storage](/platform/self-hosting/object-storage) for the `S3_ENDPOINT` and `S3_FORCE_PATH_STYLE` settings), and an internal SMTP relay. |
| 188 | + |
| 189 | +</Step> |
| 190 | + |
| 191 | +<Step> |
| 192 | + |
| 193 | +### Deploy with the registry override |
| 194 | + |
| 195 | +```bash |
| 196 | +helm install sim ./helm/sim \ |
| 197 | + --namespace simstudio --create-namespace \ |
| 198 | + --values airgapped-values.yaml |
| 199 | +``` |
| 200 | + |
| 201 | +</Step> |
| 202 | + |
| 203 | +<Step> |
| 204 | + |
| 205 | +### Disable what cannot work |
| 206 | + |
| 207 | +```yaml |
| 208 | +app: |
| 209 | + env: |
| 210 | + NEXT_PUBLIC_CHAT_DISABLED: "true" |
| 211 | +``` |
| 212 | + |
| 213 | +Disable CronJobs whose features you cannot use, and skip the integrations you have no egress for. |
| 214 | + |
| 215 | +</Step> |
| 216 | + |
| 217 | +<Step> |
| 218 | + |
| 219 | +### Verify |
| 220 | + |
| 221 | +Run the [verification checklist](/platform/self-hosting/verify), skipping the steps that depend on unavailable features. Steps 8 (knowledge base) and 10 (integrations) are expected to fail in a fully disconnected deployment. |
| 222 | + |
| 223 | +</Step> |
| 224 | + |
| 225 | +</Steps> |
| 226 | + |
| 227 | +## Upgrades |
| 228 | + |
| 229 | +Every upgrade means re-mirroring images at the new tag before running `helm upgrade`. Mirror all three of app, realtime, and migrations at the **same** tag — see [Upgrades](/platform/self-hosting/upgrades). |
| 230 | + |
| 231 | +Read the release notes from a connected machine first; there is no in-product changelog. |
| 232 | + |
| 233 | +<FAQ items={[ |
| 234 | + { question: "Does Sim require a license server?", answer: "Not for the core deployment — app, realtime, and migrations have no license check or activation step. The Chat module calls www.copilot.sim.ai with COPILOT_API_KEY and can be disabled with NEXT_PUBLIC_CHAT_DISABLED, and the optional self-hosted Copilot service requires a Sim-issued LICENSE_KEY."}, |
| 235 | + { question: "Can I use knowledge bases in an air-gapped deployment?", answer: "No. Embeddings require a hosted provider — OpenAI, Azure OpenAI, or Gemini (KB_EMBEDDING_MODEL=gemini-embedding-001) — and there is no local embedding backend, so knowledge bases need egress to one of those endpoints. Running Ollama locally does not help: embeddings do not route through the configured chat model."}, |
| 236 | + { question: "Why do I get ImagePullBackOff on Postgres or Redis after setting imageRegistry?", answer: "global.useRegistryForAllImages defaults to false, so only simstudioai/* images route through your registry — third-party images still point at Docker Hub. Set it to true and mirror pgvector, redis, curl, ollama, the OTel collector, and busybox as well."}, |
| 237 | + { question: "Can I run local models instead of hosted providers?", answer: "Yes. Ollama, vLLM, and LiteLLM all work as OpenAI-compatible endpoints inside your network, and the chart can deploy Ollama in-cluster with optional GPU support. Models must be transferred in or pulled during a connected window." }, |
| 238 | +]} /> |
0 commit comments