Self-Hosting & Air-Gapped
Where each open-source component lives, how the local stack fits together, and step-by-step instructions to deploy and verify Hanzo locally or on an air-gapped host.
This guide covers how to deploy, wire, and smoke-test a local or air-gapped Hanzo installation from clean source and prebuilt binaries, with no connection to api.hanzo.ai.
Where everything lives
The hosted cloud (api.hanzo.ai) and a self-hosted installation are built from the same open-source repositories under github.com/hanzoai. Each repository owns one distinct boundary:
| Component | Repository | Runtime | Default local ports | What runs offline today |
|---|---|---|---|---|
| Cloud Core | hanzoai/cloud | Single static Go binary (CGO_ENABLED=0) | 127.0.0.1:8080 (HTTP + console), 127.0.0.1:9090 (/healthz, /readyz, /metrics), 127.0.0.1:9653 (ZAP) | Encrypted SQLite document + KV store (/v1/base), durable task queue (/v1/tasks), functions (/v1/functions), embedded KMS (/v1/kms), MCP JSON-RPC (/mcp), OpenAPI spec (/v1/openapi.json) |
| Model Engine | hanzoai/engine | Single Rust binary (hanzoai or hanzo-engine) | 127.0.0.1:1234 (HTTP + /ui) | Local LLM, vision, audio, and embedding inference (/v1/models, /v1/chat/completions, /v1/embeddings, Anthropic /v1/messages) plus a built-in browser chat UI at /ui |
| Identity (IAM) | hanzoai/iam | Single Go binary + embedded SQLite | 127.0.0.1:8000 (HTTP) | OpenID Connect discovery, OAuth2 PKCE, JWKS (/v1/iam/.well-known/jwks), SCIM 2.0, and org JWTs for multi-tenant cloud and /v1/kms secrets |
| Agentic Bot | hanzoai/bot | Node.js 22+ runtime (hanzo-bot / @hanzo/bot) | ws://127.0.0.1:18789 (gateway) | Local multi-channel & CLI autonomous agent runtime wired to local engine (:1234) and local cloud (:8080) |
| AI Router | hanzoai/ai | Go binary (cmd/aid) or ghcr.io/hanzoai/ai | :8000 (configurable) | Multi-model routing, metering, and RAG across local engine nodes or upstream providers (optional for single-node local setups) |
| Chat Client | hanzoai/chat | Static Vite + React SPA (@hanzo/chat) | localhost:3090 (dev) or any static file host | Multi-conversation web workspace pointed at a local stack via VITE_HANZO_API and VITE_HANZO_IAM (optional; engine already includes an embedded /ui) |
| CLI | hanzoai/cli | Single Rust binary (hanzo) | Client only | Model pulls (hanzo pull), local engine launcher (hanzo engine serve), terminal coding agent, and /v1 + MCP client |
Start small. You do not need Kubernetes, PostgreSQL, Redis, or all seven repositories to validate an installation. For a clean local or air-gapped test, start with Cloud Core (hanzoai/cloud) and Model Engine (hanzoai/engine) — two static binaries backed by local files — and optionally attach Agentic Bot (hanzoai/bot) or IAM (hanzoai/iam).
Local architecture
Browser / OpenAI SDK / hanzo-bot / curl
│
├──► 127.0.0.1:1234 hanzoai/engine (LLM + embeddings: /v1/chat/completions, /v1/models, /ui)
│ │
│ └──► ~/.cache/huggingface/hub (Model snapshots populated by `hanzo pull`)
│
├──► 127.0.0.1:8080 hanzoai/cloud (Data, queues, functions, KMS, MCP: /v1/base, /v1/tasks, /mcp)
│ │
│ └──► .dev/data/ (SQLCipher-encrypted SQLite files + .dev/master.key)
│
├──► 127.0.0.1:18789 hanzoai/bot (Optional local agentic gateway wired to :1234 + :8080)
│
└──► 127.0.0.1:8000 hanzoai/iam (Optional OIDC + JWKS issuer for org JWTs & /v1/kms)Deploy & test Cloud Core (hanzoai/cloud)
Prerequisites: Go 1.26.5+ and openssl (macOS or Linux; no C compiler, Docker, or Rust toolchain required).
Build and start
git clone https://github.com/hanzoai/cloud
cd cloud
make devWhat make dev does on first boot:
- Compiles
./cloudwithCGO_ENABLED=0(pure-Go SQLite + SQLCipher encryption, zero C dependencies). - Generates a 32-byte encryption key at
.dev/master.key(0600) and stores encrypted state under.dev/data. - Binds loopback only (
127.0.0.1:8080for HTTP and the embedded console,127.0.0.1:9090for health/metrics,127.0.0.1:9653for ZAP) and enables staged local functions (CLOUD_ENABLE_STAGED=functions).
To run without make (for example in a systemd unit or air-gapped startup script):
CGO_ENABLED=0 go build -o cloud ./cmd/cloud
mkdir -p .dev && (umask 077 && openssl rand -base64 32 > .dev/master.key)
CLOUD_LISTEN=127.0.0.1:8080 \
CLOUD_DATA_DIR=.dev/data \
CLOUD_ENABLE_STAGED=functions \
CLOUD_KMS_MASTER_KEY_REF="$(cat .dev/master.key)" \
./cloudSmoke-test the installation
From a second terminal, run these four checks to confirm health, encrypted storage, MCP tool discovery, and the OpenAPI contract:
# 1. Liveness check (answers on port 9090)
curl -sS http://127.0.0.1:9090/healthz
# 2. Write a JSON document to the embedded SQLCipher store (/v1/base)
curl -sS -X POST http://127.0.0.1:8080/v1/base/collections/notes \
-H "Content-Type: application/json" \
-d '{"doc":{"title":"airgap-smoke-test"}}'
# 3. Read the collection back
curl -sS http://127.0.0.1:8080/v1/base/collections/notes
# 4. Verify the MCP JSON-RPC server exposes the registered tools
curl -sS -X POST http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Expected responses:
/healthzreturns{"status":"ok"}.POST /v1/base/collections/notesreturns the stored record with generatedidand timestamps.GET /v1/base/collections/notesreturns{"records":[...]}containing your document.POST /mcpreturns"tools":[...]built from the exact same typed operation registry ashttp://127.0.0.1:8080/v1/openapi.json.
Loopback & unauthenticated namespace: Without an Authorization: Bearer <jwt> header, cloud writes to a dedicated local namespace so single-developer testing works with zero setup. Never change CLOUD_LISTEN from 127.0.0.1 to 0.0.0.0 until you place Hanzo IAM in front of it. Also keep .dev/master.key safe — encrypted SQLite stores can only be opened by the key that created them (or set CLOUD_DEV_UNENCRYPTED=1 for unencrypted scratch runs).
Deploy & test local model inference (hanzoai/engine)
hanzoai/engine serves open-weight models (Zen, Qwen, Llama, Gemma, DeepSeek, Mistral) over OpenAI-compatible /v1/chat/completions and /v1/embeddings, Anthropic-compatible /v1/messages, plus a self-contained web UI at /ui.
Download or build the binary
Release tarballs are published at github.com/hanzoai/engine/releases/latest for macOS (arm64 / amd64 with Metal) and Linux (amd64 / arm64 CPU, with cosign signatures):
curl -fL https://github.com/hanzoai/engine/releases/latest/download/hanzoai-macos-arm64.tar.gz | tar xzcurl -fL https://github.com/hanzoai/engine/releases/latest/download/hanzoai-linux-amd64.tar.gz | tar xzRequires Rust 1.88 or newer (install.sh auto-detects CUDA, Metal, or CPU features and installs hanzo-engine into ~/.cargo/bin):
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/hanzoai/engine/main/install.sh | shPull and start a local model (zen-nano, ~1.2 GB RAM)
With the hanzo CLI (curl -fsSL https://hanzo.sh | sh) and hanzo-engine installed, pull any open-weight model into ~/.cache/huggingface/hub and serve it locally:
# 1. Pull model weights locally (resolves short aliases or any <org>/<model> ID)
hanzo pull zen-nano
# 2. List locally cached models
hanzo engine list
# 3. Serve on http://127.0.0.1:1234/v1 (with built-in browser chat UI at /ui)
hanzo engine serve zen-nano -- --host 127.0.0.1 -p 1234 --no-advertise(If you extracted the standalone ./hanzoai release binary without the hanzo CLI, run ./hanzoai --serve-ip 127.0.0.1 --port 1234 run -m zenlm/zen-nano-0.6b.)
Smoke-test inference
# 1. Confirm the model is loaded
curl -sS http://127.0.0.1:1234/v1/models
# 2. Run a local completion (standard OpenAI wire format)
curl -sS http://127.0.0.1:1234/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "zenlm/zen-nano-0.6b",
"messages": [{"role": "user", "content": "Reply with the single word pong."}],
"max_tokens": 256
}'Any OpenAI-compatible SDK or agent can now use base_url="http://127.0.0.1:1234/v1" with any non-empty api_key string.
Deploy & test the local Agentic Bot (hanzoai/bot)
hanzoai/bot (@hanzo/bot / hanzo-bot) runs an autonomous tool-using agent gateway locally against your hanzo-engine (:1234) and cloud (:8080) endpoints — with no external API keys required.
# 1. Point a minimal config at your local engine (:1234)
mkdir -p ~/.hanzo-local-bot/workspace
cat > ~/.hanzo-local-bot/bot.json <<'EOF'
{
"agents": {
"defaults": {
"workspace": "~/.hanzo-local-bot/workspace",
"model": { "primary": "local-engine/zenlm/zen-nano-0.6b" }
}
},
"models": {
"mode": "replace",
"providers": {
"local-engine": {
"baseUrl": "http://127.0.0.1:1234/v1",
"apiKey": "local",
"api": "openai-completions",
"models": [
{
"id": "zenlm/zen-nano-0.6b",
"name": "Zen Nano 0.6B (Local)",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
"maxTokens": 4096
}
]
}
}
}
}
EOF
# 2. Start the local gateway (Terminal A)
BOT_SKIP_CHANNELS=1 BOT_STATE_DIR=~/.hanzo-local-bot \
BOT_CONFIG_PATH=~/.hanzo-local-bot/bot.json \
hanzo-bot gateway run --port 18789 --auth none
# 3. Run an end-to-end agent turn against local Cloud Core (:8080) (Terminal B)
BOT_STATE_DIR=~/.hanzo-local-bot BOT_CONFIG_PATH=~/.hanzo-local-bot/bot.json \
hanzo-bot agent --session-id smoke \
--message "Query http://127.0.0.1:8080/v1/base/collections/notes and summarize the records."Running completely air-gapped (zero outbound network)
Neither cloud, engine, nor hanzo-bot phones home, requires a license server, or makes background telemetry calls. Once model weights are cached via hanzo pull (or copied into a local directory), hanzo-engine resolves weights directly from disk with no special offline environment variables needed.
To stage an installation onto an air-gapped network:
Step A — On a connected staging machine
mkdir -p hanzo-airgap/{bin,weights}
# 1. Build the static cloud binary (pure Go, zero dynamic libc links)
git clone https://github.com/hanzoai/cloud
(cd cloud && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ../hanzo-airgap/bin/cloud ./cmd/cloud)
# 2. Fetch the Linux engine binary (or compile from source with CUDA for NVIDIA GPUs)
curl -fL https://github.com/hanzoai/engine/releases/latest/download/hanzoai-linux-amd64.tar.gz \
| tar xz -C hanzo-airgap/bin
# 3. Pull model weights locally (either via `hanzo pull` into ~/.cache/huggingface/hub
# or directly into a portable weights folder)
hanzo pull zen-nano
cp -RL ~/.cache/huggingface/hub/models--zenlm--zen-nano-0.6b/snapshots/* \
hanzo-airgap/weights/zen-nano-0.6b
# 4. Archive for transfer
tar czf hanzo-airgap.tar.gz hanzo-airgap/(If your policy requires compiling inside the air-gapped enclave instead of transferring binaries, run go mod vendor inside cloud and cargo vendor inside engine before archiving the source trees.)
Step B — On the air-gapped target host
tar xzf hanzo-airgap.tar.gz
cd hanzo-airgap
# 1. Start Cloud Core
mkdir -p data && (umask 077 && openssl rand -base64 32 > master.key)
CLOUD_LISTEN=127.0.0.1:8080 \
CLOUD_DATA_DIR=./data \
CLOUD_ENABLE_STAGED=functions \
CLOUD_KMS_MASTER_KEY_REF="$(cat master.key)" \
./bin/cloud &
# 2. Start Model Engine directly from the staged weights directory
./bin/hanzoai --serve-ip 127.0.0.1 --port 1234 \
run -m ./weights/zen-nano-0.6b &Re-run the curl smoke tests from Sections 1 and 2 against 127.0.0.1:9090/healthz, 127.0.0.1:8080/v1/base/collections/notes, and 127.0.0.1:1234/v1/chat/completions to verify both services are healthy offline.
Optional: Add local Identity & KMS secrets (hanzoai/iam)
hanzoai/cloud delegates authentication to an OIDC issuer via {CLOUD_IAM_ISSUER}/v1/iam/.well-known/jwks (defaulting to http://127.0.0.1:8000). Add hanzoai/iam when you want multi-tenant org isolation or sealed secrets in /v1/kms:
git clone https://github.com/hanzoai/iam
cd iam
# Generate a local dev signing key + seed file once
mkdir -p .dev/keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out .dev/keys/cert-dev
printf '{"organizations":[{"owner":"admin","name":"hanzo"}],"certs":[{"owner":"admin","name":"cert-dev","cryptoAlgorithm":"RS256"}],"applications":[{"owner":"admin","name":"hanzo-dev","clientId":"hanzo-dev","organization":"hanzo","enablePassword":true}]}\n' > .dev/init_data.json
# Boot local OIDC + JWKS on 127.0.0.1:8000 (ZAP on :19653 so it can run beside cloud's :9653)
IAM_DEV_HOST_RELATIVE=1 IAM_SIGNING_KEYS=.dev/keys \
go run . serve --http http://127.0.0.1:8000 --zap 127.0.0.1:19653 --init-data .dev/init_data.jsonVerify OIDC discovery and JWKS:
curl -sS http://127.0.0.1:8000/.well-known/openid-configuration
curl -sS http://127.0.0.1:8000/v1/iam/.well-known/jwksWith a bearer token issued by that local IAM (owner claim matching <org>), /v1/kms seals and unwraps secrets under .dev/data/orgs/<org>/kms.db:
curl -sS -X POST http://127.0.0.1:8080/v1/kms/orgs/<org>/secrets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"UPSTREAM_KEY","env":"dev","value":"example-secret"}'
curl -sS "http://127.0.0.1:8080/v1/kms/orgs/<org>/secrets/UPSTREAM_KEY?env=dev" \
-H "Authorization: Bearer $TOKEN"Optional web chat client (hanzoai/chat)
If you want the full multi-project web chat workspace (@hanzo/chat) in addition to hanzo-engine's built-in /ui, point its Vite build at your local API and IAM origins:
git clone https://github.com/hanzoai/chat
cd chat
pnpm install --no-frozen-lockfile
VITE_HANZO_API=http://127.0.0.1:8080 VITE_HANZO_IAM=http://127.0.0.1:8000 pnpm devThis serves the web client on http://localhost:3090.
Known current boundaries
To avoid surprises during evaluation, note three active boundaries in the current open-source cut:
- CLI loopback host (
hanzoai/cloud#4): Runninghanzo host serveorhanzo network use localin thehanzoCLI currently expects a separatehosthelper binary and stops withno local cloud host. Startclouddirectly viamake devor./cloudand call its HTTP (:8080) or MCP (/mcp) endpoints directly. /v1/basereplaces standalone local/v1/kvand/v1/sql: In localcloud, document collections, key-value records, and SQLite tables all live behind/v1/base.- External subsystems fail closed: Routes in
cloudthat proxy optional companion daemons (/v1/s3on127.0.0.1:9000,/v1/dnson127.0.0.1:8443,/v1/execon127.0.0.1:8100) return an explicit503until their upstream environment variables (S3_ENDPOINT,HANZO_DNS_URL,CODE_EXEC_UPSTREAM) are configured.
Real-time assistance & support
If you hit a missing detail while standing up a local, on-prem, or air-gapped environment:
- Discord (fastest path to core engineers): Join discord.gg/XthHQQj — post in
#generalor#docsfor direct help from the engineers maintainingcloud,engine, andiam. - GitHub Issues: File reproduction steps or doc gaps directly on
hanzoai/cloud,hanzoai/engine,hanzoai/iam, orhanzo-docs/docs. - Enterprise & air-gapped architecture onboarding: Email [email protected] for dedicated deployment assistance, custom model weights, and security review support.