Air-Gapped Deployment

What you'll accomplish: Deploy Hoziron in an environment with zero outbound network access — local inference (Ollama or vLLM), fully offline carrier-licence verification, and offline package transfer.

Overview

Air-gapped deployments use local inference servers instead of cloud LLM providers, and packages move via archive files instead of registry downloads. Per the glossary's definition of this deployment mode: single binary + local models, zero external network calls.

Licence provisioning is fully offline

This is the part that most needs spelling out precisely for a disconnected environment, since the whole platform refuses to boot without a valid carrier licence at $HOZIRON_HOME/licence.json (see Bare metal § Carrier licence for the mechanics — this section covers why it works with no network at all).

Verification is a local Ed25519 signature check, not a phone-home. The source (crates/platform/hoziron-core/src/routing/licence.rs) states this directly: "The licence encodes the full commercial agreement... No phone-home required." LicenceValidator::load_signed (the production path, used by HozironPlatform::init on every boot):

  1. Reads licence.json from disk.
  2. Verifies the Ed25519 signature over the payload against a public key compiled into the binary (HOZIRON_LICENCE_PUBKEY, a constant, not fetched at runtime).
  3. Checks expiry against the local system clock.

None of these steps make a network call. This is a genuine, verified selling point for air-gapped carriers: the licence that gates the whole platform never calls out to Hoziron's infrastructure, on any boot, ever.

Getting the licence onto the disconnected network: Hoziron issues the signed licence.json on its own side and sends it to you. Carry that file across the air gap (USB, one-way transfer station, whatever your process requires) like any other artifact. Once it's on the disconnected machine, everything above is local — no further network step is needed.

Licence trust is bound to the carrier ID, not to any machine identity, so there's no per-host or per-container derivation step to validate before shipping a licence across the air gap — the same licence.json is valid on any host running that carrier's instance. (A separate, local-only advisory lock still resolves a machine identifier at boot to stop two processes on one box from racing on the same licence file — that's a single-instance concern, not part of licence trust, and a mismatch there only fails to acquire the lock rather than affecting validity.)

Local models with Ollama

config.toml uses the [[provider]] inventory (ADR-053) — each entry declares an explicit driver, and sovereignty (local vs. cloud) is resolved from that driver's compile-time disposition, never inferred from the URL:

[[provider]]
id = "ollama"
driver = "ollama"
url = "http://ollama.internal:11434/v1"

  [[provider.model]]
  id = "llama3.1:70b"
  context_window = 128000

[routing_gateway]
local_preference = [{ provider = "ollama", model = "llama3.1:70b" }]

An empty [[provider]] list is valid — with no providers configured, local_only/force_local routing falls back to the safe, network-free echo driver rather than failing, but that's a placeholder, not a real inference path. For an actual air-gapped deployment you want a real ollama (or vllm) entry with a non-empty local_preference.

Start Ollama with OLLAMA_HOST=0.0.0.0 ollama serve if Hoziron connects from a different host or container.

Local models with vLLM

vLLM exposes an OpenAI-compatible API. Point an openai-compatible driver entry at it:

[[provider]]
id = "vllm"
driver = "openai-compatible"
url = "http://vllm.internal:8000/v1"
api_key_env = "VLLM_API_KEY"   # vLLM accepts any non-empty key
sovereignty_attestation = "carrier confirms this endpoint is in-boundary"

  [[provider.model]]
  id = "meta-llama/Llama-3.1-70B-Instruct"

[routing_gateway]
local_preference = [{ provider = "vllm", model = "meta-llama/Llama-3.1-70B-Instruct" }]

The sovereignty_attestation field is required to treat an openai-compatible endpoint as Local — omit it and the entry fails closed to Cloud (ADR-053: no implicit fallback). See Hardening checklist § Sovereignty attestation is a trust assumption before attesting a self-hosted endpoint as local — it's a carrier claim the platform trusts, not something it verifies.

Docker networking for local models

When both Hoziron and the inference server run in containers:

services:
  hoziron:
    depends_on:
      - ollama

  ollama:
    image: ollama/ollama:latest
    ports:
      - "11434:11434"

In config.toml, reference the service name:

[[provider]]
id = "ollama"
driver = "ollama"
url = "http://ollama:11434/v1"

Offline package transfer

hoziron-cli catalog export/import move a single installed package as a portable .tar bundle, with the content hash verified locally on import — no network required either direction:

# On a connected machine: export an installed package
hoziron-cli catalog export claims-intake --output claims-intake.tar

# Transfer the .tar file to the air-gapped environment
# (USB drive, SCP over an internal network, a one-way transfer station, etc.)

# On the air-gapped machine: import
hoziron-cli catalog import ./claims-intake.tar

Collections: no bulk export/import — transfer packages individually

There is no hoziron-cli collection export or hoziron-cli collection import command — CollectionCommands only has init, list, install, inspect, publish, and validate. A collection (COLLECTION.toml) is just a manifest of package names and versions; there's no single-command bulk archive for it. For an air-gapped transfer of an entire collection:

# On a connected machine: see what's in the collection
hoziron-cli collection inspect insurance-starter

# Export each listed package individually
hoziron-cli catalog export claims-intake --output claims-intake.tar
hoziron-cli catalog export policy-lookup --output policy-lookup.tar
# ...repeat for every package the collection lists

# Transfer all the .tar files across, then on the air-gapped machine:
hoziron-cli catalog import ./claims-intake.tar
hoziron-cli catalog import ./policy-lookup.tar
# ...

If you need this often, scripting the export loop over hoziron-cli collection inspect --json is worth doing — there's no built-in equivalent.

Checklist

  • Licence issued for the carrier ID and installed at $HOZIRON_HOME/licence.json
  • hoziron-cli licence status confirms it's loaded and active once the server is up
  • Local inference server (Ollama or vLLM) running and reachable
  • [[provider]] configured with the correct driver and, for openai-compatible endpoints, sovereignty_attestation set
  • routing_gateway.local_preference non-empty (an empty provider list silently falls back to the network-free echo driver, not a real model)
  • All required packages transferred and imported (hoziron-cli catalog export/import, per-package — no bulk collection transfer exists)
  • Health check passes: hoziron-cli health
  • Test an agent against a local model: hoziron-cli agent chat <id>

Next steps


Related: