Install via Docker
What you'll accomplish: Get a Hoziron image, mount config and a carrier licence, run the container, and verify the health endpoint.
A note on image availability
There is no public image at ghcr.io/hozironos/hoziron. Hoziron issues you a private registry pull credential:
- You have Hoziron-issued registry access — pull the tag Hoziron gives you, after logging in with the credentials Hoziron provides.
- Ask Hoziron for a build if you don't have registry access.
The rest of this page documents the container's external contract (env vars, ports, volumes, --surfaces flag), which holds regardless of how you obtain the image.
1. Get a carrier licence
Exactly as with a binary install — the container refuses to do meaningful work without a signed licence at $HOZIRON_HOME/licence.json, and HOZIRON_HOME=/data inside the image. There's no way to skip this by "just being in a container."
Licence trust is bound to your carrier ID, not to any per-container or per-host machine identity, so there's no container-specific step to get the licence issued — send your carrier ID/name to Hoziron exactly as described in Getting Started: the carrier licence gate, and the resulting licence.json is portable to any container running that carrier's instance.
Getting the resulting licence.json onto the /data volume: docker cp works fine even though the distroless image has no shell (docker cp talks to the container's filesystem through the Docker daemon, not through a shell inside the container) — but it only works against a container that already exists, so either start the container once first (it will crash-loop until the licence is present, which is fine), or mount the licence file directly as a read-only bind mount at container start instead:
docker run -d --name hoziron -p 4200:4200 -p 4210:4210 \
-v "$(pwd)/licence.json:/data/licence.json:ro" \
-v hoziron-data:/data \
<your-image-ref>
(Bind-mounting licence.json read-only alongside the writable hoziron-data volume is the simplest option in practice — no debug image needed.)
See Getting Started: the carrier licence gate for the full licence-provisioning flow.
2. Create a config file
Declare an explicit [[provider]] inventory (ADR-053):
# config.toml
[[provider]]
id = "anthropic"
driver = "anthropic"
api_key_env = "ANTHROPIC_API_KEY"
[[provider.model]]
id = "claude-sonnet-4-20250514"
[routing_gateway.standard]
preference = [{ provider = "anthropic", model = "claude-sonnet-4-20250514" }]
[auth]
mode = "local"
auth.mode = "local" matters here: the daemon refuses to boot with auth disabled while bound to a non-loopback address, and inside a container you're binding 0.0.0.0 (loopback-only binds aren't reachable via -p port publishing). See step 5.
3. Run the container
docker run -d --name hoziron -p 4200:4200 -p 4210:4210 \
-e ANTHROPIC_API_KEY="sk-ant-..." \
-v "$(pwd)/config.toml:/data/config.toml:ro" \
-v hoziron-data:/data \
<your-image-ref>
Key points:
HOZIRON_HOME=/dataandHOZIRON_LOG=infoare already set as image defaults (see the rootDockerfile) — override with-eif needed.- Config, provider keys, and (from step 1)
licence.jsonall live under the read-write/datavolume; everything else is read-only. - The container runs as UID 65532 (distroless nonroot) with a read-only root filesystem.
- The image's default
CMDis--surfaces api,registry(ports 4200 and 4210). Adddashboardto--surfacesto also serve the web console — see Dashboard. - Provider keys can be passed as
-eenv vars (read directly by the server process) or set later viahoziron-cli config set-key <provider>, which writes to the daemon's own vault over the API — there is no.envfile inside the container.
4. Create the first admin API key
With auth.mode = "local", the very first POST /auth/keys succeeds without a bearer token — the server allows exactly one unauthenticated bootstrap call while the key store is empty, then locks down:
curl -X POST http://localhost:4200/auth/keys \
-H "Content-Type: application/json" \
-d '{"name": "bootstrap-admin", "role": "admin"}'
# {"id": "...", "name": "bootstrap-admin", "role": "admin", "secret": "hzk_..."}
Save the returned secret — it's shown once. Point the CLI at the container and use it:
hoziron-cli context add docker --url http://localhost:4200 --auth token --token hzk_...
hoziron-cli context use docker
5. Verify health
curl http://localhost:4200/health
{
"status": "healthy",
"uptime_secs": 12,
"latency_p95_ms": null,
"version": "0.5.0",
"agents": { "running": 0, "suspended": 0, "total": 0 },
"providers": { "healthy": 1, "degraded": 0, "unavailable": 0 },
"memory": { "state": "healthy" }
}
/health is served by the api surface and reflects platform internals (agents, providers, memory). There's also a lightweight /__server/health on every bound listener that just confirms the process is up and which surfaces are attached to that port — useful for container/orchestrator liveness probes when you don't want to depend on the platform having finished booting (e.g. licence problems will make /health unreachable but /__server/health still needs the process to have started). Note the distroless runtime image has no curl/wget, so the container's own HEALTHCHECK (see docker-compose.yml) probes liveness with hoziron-server --version instead — do your real health probing from outside the container.
Using Docker Compose
The repo's root docker-compose.yml runs hoziron plus an optional Caddy reverse proxy (--profile proxy), mapping ports 4200 and 4210, mounting a named hoziron-data volume at /data, and reading ANTHROPIC_API_KEY/etc. from a local .env file via env_file: (this is Compose's own env-injection mechanism — still not something the Hoziron server reads as a file itself):
services:
hoziron:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
environment:
- HOZIRON_HOME=/data
- HOZIRON_LOG=${HOZIRON_LOG:-info}
volumes:
- hoziron-data:/data
ports:
- "4200:4200"
- "4210:4210"
read_only: true
tmpfs:
- /tmp
Put your licence at /data/licence.json on the hoziron-data volume (see step 1) and your config.toml alongside it before docker compose up -d.
Connecting to local models (Ollama)
If you're using Ollama on the host machine:
docker run -d --name hoziron -p 4200:4200 \
-v "$(pwd)/config.toml:/data/config.toml:ro" \
-v hoziron-data:/data \
--add-host=host.docker.internal:host-gateway \
<your-image-ref>
In your config.toml:
[[provider]]
id = "ollama"
driver = "ollama"
url = "http://host.docker.internal:11434/v1"
[[provider.model]]
id = "llama3.1:70b"
[routing_gateway.local]
preference = [{ provider = "ollama", model = "llama3.1:70b" }]
Start Ollama with OLLAMA_HOST=0.0.0.0 ollama serve so it's reachable from Docker's bridge network. A licence is still required even for this fully-local setup.
Next steps
You've got a running, licensed container. Now create your first agent.
Related: