> For the complete documentation index, see [llms.txt](https://docs.drizz.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.drizz.dev/twins/self-hosting/air-gapped.md).

# Air-gapped installs

Install twins on a network with no internet, from one bundle that carries every image across.

Install twins on a network with no internet, from one bundle that carries every image across.

{% hint style="info" %}
**Getting the install files.** The Docker Compose file, Helm chart, Terraform modules and twins images are shared with you by Drizz. Ask your Drizz contact for access before you start.
{% endhint %}

|               |                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------ |
| **Runs on**   | Docker Compose · Kubernetes · private networks on AWS, Google Cloud and Azure              |
| **You need**  | A machine with internet to make the bundle, and Docker 28 or later there                   |
| **Bundle**    | Every image, the Compose file, its settings example and the chart, with a SHA-256 checksum |
| **Watch out** | A bundle carries one platform. Pick it with `PLATFORM` (default `linux/amd64`).            |

`deploy/airgap.sh` carries everything across in one file: every image the Compose file and the Helm chart run, the Compose file and its settings example, and the chart.

## Prerequisites

* A machine with internet, to make the bundle.
* Docker 28 or later on it, for `docker save --platform`. An older Docker saves the platform it pulled.
* For Kubernetes: a private registry. `crane` or `skopeo`, where installed, reads each image's digest back.

## What leaves your network

Nothing in twins calls out. The only traffic that leaves the network is your agent's own model calls.

{% hint style="info" %}
A world founded from a written `brief` needs a model the server can reach (`OPENAI_API_KEY`). A baseline, a `scenario`, a `population` or a `size` needs none.
{% endhint %}

## Make the bundle

On a machine with internet:

```bash
PLATFORM=linux/amd64 deploy/airgap.sh bundle twins-airgap.tar.gz
```

This writes `twins-airgap.tar.gz` and its SHA-256 checksum beside it (`twins-airgap.tar.gz.sha256`). Inside is a manifest naming each image and the digest its registry served.

* `PLATFORM` picks the architecture saved. The default is `linux/amd64`.
* `TWINS_IMAGE` names the twins image to bundle, if not the chart's own version.
* A bundle carries one platform.

Carry both files across.

## Install it

On the machine without internet, the script refuses a bundle that does not match its checksum, and one missing an image its manifest names.

{% tabs %}
{% tab title="Docker Compose" %}
{% stepper %}
{% step %}

### Load the images

```bash
deploy/airgap.sh load twins-airgap.tar.gz
```

This loads every image into the machine's Docker.
{% endstep %}

{% step %}

### Start twins

The bundle holds a `deploy` folder with the Compose file, its settings example and the chart. Take it out of the bundle:

```bash
tar -xzf twins-airgap.tar.gz deploy
```

The bundle's copy of `deploy/selfhost.env.example` names the images it holds (`TWINS_IMAGE`, `TWINS_POSTGRES_IMAGE`, `TWINS_CACHE_IMAGE`). A `deploy/.env` made from it runs those. Then follow [Docker Compose](/twins/self-hosting/docker-compose.md) as usual:

```bash
docker compose -f deploy/docker-compose.selfhost.yml up -d
```

Nothing is pulled.
{% endstep %}
{% endstepper %}
{% endtab %}

{% tab title="Kubernetes" %}
{% stepper %}
{% step %}

### Push the images to your registry

```bash
deploy/airgap.sh push twins-airgap.tar.gz harbor.acme.internal/twins
```

`push` loads the bundle, then pushes each image into your private registry. Each image keeps its repository and tag under that registry (`drizzdev/twins`, `library/postgres`).
{% endstep %}

{% step %}

### Install from `airgap-values.yaml`

`push` writes `airgap-values.yaml`. It holds:

* `global.imageRegistry`: your registry.
* Each image pinned to the digest your registry now holds. The script reads it back with `crane` or `skopeo` where either is installed.

```bash
helm install twins deploy/charts/twins -f airgap-values.yaml
```

Set `global.imagePullSecrets` to name your registry's credentials.

{% hint style="info" %}
A bundle holds one platform. So the digest in `airgap-values.yaml` is not the multi-platform digest the chart pins by default.
{% endhint %}
{% endstep %}

{% step %}

### Check it

`helm test` and the startup wait use the twins image itself, so there is nothing else to mirror. Then make the first key as in [Kubernetes](/twins/self-hosting/kubernetes.md#steps).
{% endstep %}
{% endstepper %}
{% endtab %}
{% endtabs %}

## Private networks in the cloud

The cloud modules can also run without a way out to the internet:

* **AWS:** with VPC endpoints instead of a NAT gateway, the tasks pull images from ECR. An image on GHCR needs a NAT gateway. A model or an identity provider outside the VPC needs one too. See the endpoint list in [AWS, Google Cloud and Azure](/twins/self-hosting/clouds.md).
* **Google Cloud:** with `cloud_nat = false`, the nodes reach Google's APIs and Artifact Registry alone.
* **Azure:** `acr_id` lets the nodes pull a private copy of the twins image from Azure Container Registry.

## Next

* [Docker Compose](/twins/self-hosting/docker-compose.md): settings, ports and the first key.
* [Kubernetes (Helm)](/twins/self-hosting/kubernetes.md): production values for the chart.
* [Upgrades](/twins/self-hosting/upgrades.md): move to a new version, and roll back.

***

*Last updated: 27 October 2026*
