> ## Documentation index
> Fetch the complete documentation index at: https://rolter-ai-rolter.docs7.io/llms.txt
> Ask it with GET https://rolter-ai-rolter.docs7.io/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# Air-Gapped Installation & Operation

> Install and run Rolter in fully offline, air-gapped environments: mirror-based install paths for Docker, PyPI, crates.io and source builds, every outbound network path with the setting that controls it, an operator checklist, and an offline smoke test.

Rolter is built to run in fully air-gapped environments with no public internet at
build or run time. One outbound call is on by default, the release check, and you
turn it off with `ROLTER_UPDATE_CHECK=false`. Every other call goes to an endpoint
you configure, and this page lists each one with the setting that turns it on or
off. It also shows how to install Rolter behind an internal mirror and how to
verify the deployment offline.

## Runtime egress

### The release check is on by default

Left alone, two parts of Rolter ask `https://api.github.com/repos/rolter-ai/rolter/releases/latest`
whether a newer stable release exists:

- The control plane asks once at boot and every six hours. The answer feeds the
  version hint in the dashboard footer and [`GET /api/v1/version`](/api/version).
- The `rolter` command asks beside whatever subcommand you run, `gateway`,
  `control`, `easy-up` and `check` included, and prints one line to stderr when a
  newer release exists. A successful answer is cached for 24 hours in
  `~/.cache/rolter/update-check.json`. A failed one is not cached, so a host that
  cannot reach GitHub tries again on every invocation.

The request is one `GET` with a 5-second timeout and a `User-Agent: rolter/<version>`
header. It carries no installation id, configuration or credentials. A failure is
logged at `debug`, never delays a command and never changes an exit status, so an
enclave keeps working with the check left on. The attempts still show up in a
firewall or DNS log, and a policy that treats an unexpected external lookup as an
incident will flag them.

Turn it off with one environment variable:

```bash
export ROLTER_UPDATE_CHECK=false   # also 0, no or off
```

Set it on the control plane and on every host or container that runs the `rolter`
command. With the Docker image that is every container, because its default command
is `rolter easy-up`. The `rolter-control` binary carries the control plane's check;
`rolter-gateway` run directly has none.

With the Helm chart, `control.updateCheck: false` reaches the control plane and the
gateway pods, including their preflight init containers, which also run `rolter check`:

```yaml
control:
  updateCheck: false
```

With the check off, the dashboard footer shows the running version alone and
`/api/v1/version` reports `"enabled": false`. See
[Update check](/configuration/environment-variables#update-check).

### Every outbound path

The release check is the only path that is on without configuration. Each of the
others stays idle until you set the setting in its row, so a deployment that never
sets them makes no other outbound call.

Every operator-written URL in the table below, from a provider's `api_base` to an
alert channel's `endpoint`, also passes the [egress policy](/configuration/egress)
before it is called. To refuse internal ranges, turn on `block_private` and
`block_loopback` and list the internal hosts the deployment must still reach in
`allow_hosts`.

| Path                        | What it reaches                                                                                                                                                                                                                                                                                       | Setting                                                                                                                                     | Default                                              |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Release check               | `api.github.com`, described above                                                                                                                                                                                                                                                                     | `ROLTER_UPDATE_CHECK=false` turns it off                                                                                                    | **On**                                               |
| Upstream providers          | Each provider's `api_base` and the provider's `egress_proxy` when set. Also the embeddings provider of a semantic cache and the **Test** button on the Providers screen                                                                                                                               | The providers and routes you configure                                                                                                      | None. The built-in `fake-llm` makes no outbound call |
| Postgres                    | The host in `ROLTER_DATABASE_URL`, from the control plane                                                                                                                                                                                                                                             | `ROLTER_DATABASE_URL`                                                                                                                       | Unset: in-memory store                               |
| Redis                       | The host in `ROLTER_REDIS_URL`, from both planes: budgets, rate limits, the response cache, the sign-in throttle and config pub/sub                                                                                                                                                                   | `ROLTER_REDIS_URL`, or `--redis-url` on the gateway                                                                                         | Unset                                                |
| ClickHouse                  | Request logs, captured payloads, provider health events and the control plane's analytics and alert queries                                                                                                                                                                                           | `CLICKHOUSE_URL` or `[logging].clickhouse_url`. A gateway with neither inherits the control plane's                                         | Unset: nothing is logged                             |
| Gateway to control plane    | Config poll every 5 seconds, the log destination at boot and adaptive-routing telemetry, all to the snapshot URL's host. The `/admin/*` proxy goes to the admin URL                                                                                                                                   | `ROLTER_SNAPSHOT_URL` and `ROLTER_SNAPSHOT_POLL_SECS`, `ROLTER_ADMIN_URL`                                                                   | Unset: static bootstrap config                       |
| Control plane to gateway    | Dashboard Playground requests, sent through `/gw/*`. No traffic while nobody uses the Playground                                                                                                                                                                                                      | `ROLTER_GATEWAY_URL`                                                                                                                        | `http://localhost:4000`                              |
| Alert channels              | Each enabled channel's `endpoint`: one JSON `POST` when a rule changes state. See [Alerting](/observability/alerting#delivery)                                                                                                                                                                        | **Alerting → Channels**                                                                                                                     | No channels                                          |
| OTLP export                 | The OTLP receiver, for traces, metrics and logs from both planes. See [Tracing](/observability/tracing)                                                                                                                                                                                               | `OTEL_EXPORTER_OTLP_ENDPOINT`, or its `_TRACES_`, `_METRICS_` and `_LOGS_` variants. `ROLTER_TELEMETRY_ENABLED=false` blocks every exporter | Unset: no exporter is built                          |
| Connector test              | The connector's `endpoint`: one empty OTLP request when someone presses **Test**, whether or not the connector is enabled. Rolter sends nothing else to a connector. See [Connectors](/observability/connectors)                                                                                      | **Observability → Connectors**                                                                                                              | No connectors                                        |
| Dashboard browser tracing   | The OTLP/HTTP endpoint named in the served page, called from each user's browser                                                                                                                                                                                                                      | `ROLTER_UI_OTEL_ENDPOINT`. `ROLTER_TELEMETRY_ENABLED=false` drops it                                                                        | Unset: the dashboard loads no tracing code           |
| Active health probes        | Each provider's `api_base` plus `[health].path`, every `interval_secs` (10). `also_track_via_llm_call` sends a one-token completion. See [Health checks](/reliability/health-checks)                                                                                                                  | `[health] enabled`                                                                                                                          | `false`                                              |
| Model catalogue refresh     | Each provider's model-list endpoint, every `[health].catalogue_interval_secs` (900), even with `[health]` off. It feeds `GET /v1/models`                                                                                                                                                              | `[health] catalogue_interval_secs`. Set `0` to turn it off                                                                                  | `900`                                                |
| Engine metrics scrape       | Each provider's `api_base` plus `/metrics`, every `interval_secs` (5). See [Load balancing](/concepts/load-balancing)                                                                                                                                                                                 | `[metrics_scrape] enabled`                                                                                                                  | `false`                                              |
| Provider status pages       | A provider's `status_page_url`, every `[health].status_page_interval_secs` (60), even with `[health]` off                                                                                                                                                                                             | `status_page_url` on a provider. Leave it unset, or point it at an internal mirror                                                          | Unset                                                |
| Cache-aware routing signals | A provider's `kv_events.endpoint` (a ZeroMQ subscription such as `tcp://vllm-0:5557`) and its `lmcache.endpoint` (an HTTP poll every `refresh_secs`, 2 by default). See [Cache-aware routing](/concepts/cache-aware-routing)                                                                          | `kv_events` and `lmcache` on a provider                                                                                                     | Unset                                                |
| MCP servers                 | The `url` of each registered server, when a client calls it through `/mcp/<server>`. See [MCP servers](/configuration/mcp-servers)                                                                                                                                                                    | The registered MCP servers                                                                                                                  | None                                                 |
| MCP OAuth                   | For a server that uses OAuth, the control plane fetches the server's resource metadata and its authorization server's metadata when consent starts, posts to the token endpoint when it completes, and refreshes tokens near expiry in a sweep every 60 seconds. See [MCP OAuth](/security/mcp-oauth) | MCP servers set to OAuth                                                                                                                    | None                                                 |
| Single sign-on              | For each identity provider, the control plane fetches `<issuer>/.well-known/openid-configuration` and the `jwks_uri`, and posts to the `token_endpoint`, at each sign-in. See [Single sign-on](/security/single-sign-on)                                                                              | **Governance → Single Sign-On**                                                                                                             | No providers                                         |
| Guardrail webhook           | The webhook URL: one `POST` per request, before it goes upstream. See [Guardrails](/security/guardrails)                                                                                                                                                                                              | `[guardrail_webhook] enabled`, or activating a provider under **Guardrails → Providers**                                                    | Disabled                                             |
| PII sanitizer               | `[pii_sanitizer].url`, `restore_url` when restoration is on, and `restore_stream_url` under `streaming = "restore"`. See [PII sanitizer](/security/pii-sanitizer)                                                                                                                                     | `[pii_sanitizer] enabled`                                                                                                                   | Disabled                                             |
| Webhook plugins             | The `endpoint` of each enabled plugin instance, on every matching request at its stage. See [Plugins](/configuration/plugins)                                                                                                                                                                         | **Plugins**: an instance is live while it is enabled                                                                                        | No instances                                         |

Several rows point at services you host inside the enclave, such as a guardrail
service, a Presidio sanitizer or an OpenTelemetry Collector. Rolter opens those
connections too, so they are listed.

These do not make an outbound call:

- **SCIM provisioning** is inbound only. Your identity provider calls `/scim/v2/*`
  on `ROLTER_PUBLIC_URL`, and Rolter never calls it back.
- **Invitations** send no email. Rolter returns the link and you pass it on.
- **Payload capture** writes to ClickHouse and nowhere else, and only when
  `[logging.payload_capture]` is enabled.
- **Prometheus metrics** are scraped from `/metrics` by your Prometheus. Rolter
  pushes nothing to it.
- **Built-in guardrails** and the `fake-llm` model run inside the gateway process.

`rolter check --connect` opens a TCP connection to the hosts in `ROLTER_DATABASE_URL`
and `ROLTER_REDIS_URL`, and to the mail relay in `ROLTER_SMTP_URL` when one is set. It is a command you run, not a background task.

### The dashboard and the API reference

The interactive API reference at `/docs` embeds the Scalar JS bundle in the binary
and sets `withDefaultFonts: false`, so it never reaches a CDN or `fonts.scalar.com`.
The `docs_page_is_self_contained` and `scalar_bundle_is_embedded` tests in
`crates/rolter-gateway/src/openapi.rs` enforce it.

The dashboard SPA is served as static assets by the control plane. Its fonts and
icons are vendored in the build, and it loads no third-party scripts, fonts or
styles. Its documentation links are off unless you configure a documentation host,
so an enclave with no mirror of this site simply sees no links. See
[Documentation links](/deployment/documentation-links). The footer's repository and
bug-report buttons point at `github.com`. They are ordinary links that open only
when someone clicks one, and the page loads nothing from them.

## Install paths through a mirroring proxy

Air-gapped sites usually proxy public registries through an internal mirror
(Sonatype Nexus, JFrog Artifactory, Harbor, …). Pick the path that matches how
you ship Rolter.

<Tabs>
  <Tab title="Docker image">
    Pull through a registry that proxies GHCR/Docker Hub:

    ```bash
    docker pull registry.internal.example/rolter/rolter:latest
    ```

    Or transfer a fully offline image with `docker save` / `docker load`:

    ```bash
    # on a connected host
    docker pull ghcr.io/rolter-ai/rolter:latest
    docker save ghcr.io/rolter-ai/rolter:latest -o rolter.tar

    # copy rolter.tar into the enclave, then
    docker load -i rolter.tar
    ```

  </Tab>
  <Tab title="PyPI wheel">
    Install through a Nexus/Artifactory PyPI proxy:

    ```bash
    uv tool install rolter --index-url https://nexus.internal.example/repository/pypi/simple
    # or
    pip install rolter --index-url https://nexus.internal.example/repository/pypi/simple
    ```

    Or install a downloaded wheel with no index at all:

    ```bash
    uv tool install ./rolter-<version>-py3-none-any.whl
    # or
    pip install --no-index ./rolter-<version>-py3-none-any.whl
    ```

  </Tab>
  <Tab title="crates.io">
    Point Cargo at a registry mirror via `.cargo/config.toml`:

    ```toml
    [source.crates-io]
    replace-with = "internal"

    [source.internal]
    registry = "sparse+https://nexus.internal.example/repository/cargo/"
    ```

    For a fully offline build, vendor the sources on a connected host and copy
    them in:

    ```bash
    cargo vendor vendor/                 # connected host, prints a [source] snippet
    # copy vendor/ into the enclave, add the printed snippet to .cargo/config.toml
    cargo build --workspace --offline
    ```

  </Tab>
  <Tab title="From source (cargo + bun)">
    The Rust build follows the crates.io tab. The UI needs an internal npm mirror
    for `bun install`:

    ```toml
    # ui/bunfig.toml
    [install]
    registry = "https://nexus.internal.example/repository/npm/"
    ```

    ```bash
    cd ui && bun install && bun run build
    cargo build --workspace --release --offline
    ```

  </Tab>
</Tabs>

## Operator checklist

**Set before you deploy:**

- `ROLTER_UPDATE_CHECK=false` on the control plane and wherever the `rolter` command runs. In Helm, `control.updateCheck: false` covers both Deployments.

**Must be reachable inside the enclave:**

- Every configured provider `api_base` (your internal model servers or a proxied provider endpoint).
- Postgres, Redis, and ClickHouse hosts, only for the features you enable.
- The control plane, if the gateway runs with `--snapshot-url`.
- Every endpoint you chose to configure from the table above, such as an alert webhook, an identity provider, an MCP server or a collector.

**Must NOT be required:**

- Public package registries at run time (only at install/build time, through the mirror).
- CDNs (`cdn.jsdelivr.net`, `fonts.scalar.com`, npm/unpkg). Rolter references none.
- `api.github.com`. With `ROLTER_UPDATE_CHECK=false` the release check is not attempted.
- Telemetry endpoints, unless you deliberately set `OTEL_*` to an internal collector.
- Provider status pages. Leave `status_page_url` unset.

## Offline smoke test

Verify a running gateway with zero external providers using the built-in
`fake-llm` model (deterministic, no upstream or secrets needed):

```bash
ROLTER_UPDATE_CHECK=false rolter gateway --port 4000 &
curl -s http://localhost:4000/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"fake-llm","messages":[{"role":"user","content":"hello"}]}'
```

A `200` with a lorem-ipsum completion confirms the gateway serves traffic with no
outbound calls. Open `http://localhost:4000/docs` and confirm the API reference
renders with no network requests leaving the host.
