# Air-gapped installation (/self-hosting-optional-advanced/deployment/air-gapped)

Toucan AI runs without internet access. At runtime it calls none of our services and loads no CDN
script or web font: it only calls the services you configure.

## What you provide [#what-you-provide]

- A container registry the cluster can pull from.
- An OpenAI-compatible LLM endpoint (vLLM, Ollama, LiteLLM…), with a model that supports tool calling.
- An OpenAI-compatible embeddings endpoint, with a model that returns 1024-dimension vectors, such as
  `bge-m3`.
- An SMTP server.
- PostgreSQL with the `pgvector` and `pgcrypto` extensions, Redis, and an S3-compatible bucket. The
  chart can run all three, for evaluation only.

## Mirror the images [#mirror-the-images]

From a machine with internet access, log in to `registry.distr.sh` with the credentials we provide
(`skopeo login registry.distr.sh`), then copy the images your values use into your registry. Ours go
at its root: `registry.distr.sh/toucantoco/backend:<tag>` becomes `registry.example.com/backend:<tag>`.
Third-party images keep their path minus the registry host: `docker.io/library/redis:8.8.1` becomes
`registry.example.com/library/redis:8.8.1`.

`global.imageRegistry` is emptied so that `helm template` shows where each image comes from. For
instance with `skopeo`:

```sh
helm template toucan-ai toucan-ai-<version>.tgz -f values.yaml --set global.imageRegistry= \
  | sed -nE "s/^[ -]*image: *['\"]?([^'\" ]+).*/\1/p" | sort -u \
  | while read -r src; do
      dst=${src#registry.distr.sh/toucantoco/}
      skopeo copy --all "docker://$src" "docker://registry.example.com/${dst#*/}"
    done
```

If your registry is not reachable from that machine, copy each image the same way to an archive
(`oci-archive:redis.tar`), carry the archives over, then copy them to your registry.

## Configure the chart [#configure-the-chart]

`global.imageRegistry` points every image at your registry, the bundled PostgreSQL, Redis and Garage included:

```yaml
global:
  imageRegistry: registry.example.com
  imagePullSecrets:
    - name: registry-credentials

backend:
  config:
    AI_DEFAULT_PROVIDER: openai-compatible
    OPENAI_COMPATIBLE_API_URL: https://llm.example.com/v1
    OPENAI_COMPATIBLE_MODEL: qwen3-32b
    EMBEDDING_API_URL: https://embeddings.example.com/v1
    EMBEDDING_DEFAULT_MODEL: bge-m3
    DOC_URL: https://docs.toucan.example.com
  envFrom:
    - secretRef:
        name: toucan-ai-secrets # OPENAI_COMPATIBLE_API_SECRET, EMBEDDING_API_SECRET…

frontend:
  config:
    DOC_URL: https://docs.toucan.example.com
    DOCS_PRODUCT_URL: https://toucan.example.com
    STATUS_PAGE_URL: ""
```

`DOC_URL` is the host the chart serves these docs on (`ingress.docs`). The assistant also reads them
from the backend pods, so your cluster's DNS must resolve that host.

An agent building a custom front imports the chart libraries it needs from a CDN, which your users'
browsers cannot reach. When you [build a front with an agent](../../core/build/build-with-an-agent.mdx),
ask it to ship them as files of the front, and check the front from a browser inside your network.
