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=/data and HOZIRON_LOG=info are already set as image defaults (see the root Dockerfile) — override with -e if needed.
  • Config, provider keys, and (from step 1) licence.json all live under the read-write /data volume; everything else is read-only.
  • The container runs as UID 65532 (distroless nonroot) with a read-only root filesystem.
  • The image's default CMD is --surfaces api,registry (ports 4200 and 4210). Add dashboard to --surfaces to also serve the web console — see Dashboard.
  • Provider keys can be passed as -e env vars (read directly by the server process) or set later via hoziron-cli config set-key <provider>, which writes to the daemon's own vault over the API — there is no .env file 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: