Deployment Topology
How Hoziron deploys in production — the unified hoziron-server binary with pluggable surfaces, the standalone registry option, container structure, networking, storage, and scaling. This document is the conceptual/architectural view; step-by-step deployment instructions live in docs/guides/deployment/.
Two Deployment Shapes
Hoziron ships one binary, hoziron-server, that can run as either shape:
- Unified (the default): one
hoziron-serverprocess serves every enabled surface — API, package registry, MCP, operations console/dashboard — selected via--surfacesor the[surfaces]block inconfig.toml. This is the common case: single host/container, single data volume, simplest operational model. - Split: the package registry runs as its own deployment — same
hoziron-serverbinary, invoked with--surfaces registry— with its own scaling, its own storage (local disk or S3-compatible object storage backend), and its own auth/key store. This is the shape for a shared catalog that must scale independently of any single carrier's agent runtime, or for a DMZ/CDN-origin placement that should not co-locate with the API surface.hoziron-registry's standalone-auth capability (bootstrap its own admin key, manage its ownkeys.db) is exactly what makes this split possible without a co-located API daemon (ADR-024).
Every surface — including a split-out registry — is served by exactly the same hoziron-server binary and the same underlying hoziron-core/kernel stack; splitting is a deployment-topology choice, not a different build.
Single-Node Process Architecture
Whichever shape is chosen, each hoziron-server instance is single-process, single-node:
The hoziron-cli CLI is not part of this diagram at all — it is a separate, stateless binary (bin/hoziron-cli) that speaks plain HTTP to whichever hoziron-server instance(s) an operator points it at; it carries no server or kernel dependency and deploys independently (typically just distributed to operator workstations, not containerized alongside the server).
Container Deployment
Container Details
| Property | Value |
|---|---|
| Base image | gcr.io/distroless/cc-debian12:nonroot |
| User | UID 65532 (nonroot) |
| Entrypoint | hoziron-server (default CMD: --surfaces api,registry) |
| Port | 4200 (API + metrics on same port) |
| Volume | /data (all persistent state) |
| Read-only filesystem | Yes (except /data) |
| Health probe | GET /health |
Environment Variables
| Variable | Purpose | Container Default |
|---|---|---|
HOZIRON_HOME | Data directory | /data |
HOZIRON_LOG | Log filter | info |
HOZIRON_LOG_FORMAT | Log format (json or text) | text (recommend json for production) |
ANTHROPIC_API_KEY | Provider key | — (inject from secrets) |
There is no HOZIRON_LISTEN environment variable — the bind address is set per surface via --api-listen/--registry-listen/--mcp-listen/--dashboard-listen or [surfaces.<name>].listen in config.toml (see TLS and networking).
Network Topology
Container / Docker
Bare Metal / VM
For bare metal, enable native TLS if no reverse proxy (set per surface, on whichever surface owns the listener — see TLS and networking):
[surfaces.api.tls]
enabled = true
cert_path = "/opt/hoziron/tls/cert.pem"
key_path = "/opt/hoziron/tls/key.pem"
Storage Layout
/data/ (HOZIRON_HOME in containers)
├── config.toml Platform configuration
├── .env API keys (local dev only)
├── hoziron.db Main SQLite database
├── packages.lock Dependency lockfile
├── data/
│ ├── memory/ Per-agent KV stores
│ └── sessions/ Conversation histories
├── auth.db API key store (mode 0600)
├── audit.db Audit trail (WAL mode)
├── packages/ Installed catalog packages
│ ├── claims-intake/
│ ├── document-ocr/
│ └── installed.json Installation metadata
├── keys/ Signing keypair
│ └── default.key Ed25519 private key (mode 0600)
├── vault/ Encrypted credentials
└── agents/ Auto-load manifest directory
├── claims-processor.toml
└── monitor-agent.toml
Storage Sizing
| Component | Growth Pattern | Typical Size |
|---|---|---|
hoziron.db | Per-agent, per-session | 10 MB – 1 GB |
data/memory/ | Per-agent, per-message | 1 MB per 1000 messages |
audit.db | Per-API-request | ~100 bytes/entry, 100K entries max |
packages/ | Per-install | 10 KB – 5 MB per package |
auth.db | Per-key | < 1 MB |
Scaling Considerations
Vertical Scaling (Recommended)
Hoziron is single-node by design. Scale by increasing resources:
| Dimension | Scale Factor |
|---|---|
| More agents | More memory (10 MB base per agent) |
| More concurrent requests | More CPU cores (tokio async) |
| Longer history | More disk (session storage) |
| More integrations | More file descriptors (MCP processes) |
Horizontal Isolation
For multi-team environments, run separate instances:
Each instance has:
- Its own config, agents, competencies, and data
- Shared registry access (install same packages)
- Shared IdP (same SSO, different role mappings)
- Shared LLM providers (separate API keys if needed)
High Availability
Hoziron does not natively support active-active HA (single-process, SQLite). For high availability:
| Approach | How | Trade-offs |
|---|---|---|
| Process supervisor restart | systemd/Docker restart: always + health probes | Seconds of downtime on crash |
| Warm standby | Second instance with shared storage (active-passive) | Manual failover |
| Stateless agents | Recreate from manifests on new instance | Lose in-flight sessions |
For most enterprise deployments, automatic process restart with graceful shutdown (30s grace period) provides sufficient availability.
Graceful Shutdown
Give the process at least 30 seconds between SIGTERM and a hard kill (e.g. systemd's TimeoutStopSec, or Docker's --stop-timeout) to allow the flush to complete.
Split-Registry Network Topology
When the registry is split out, it is a second, independent deployment with its own address, its own storage (local disk or an S3-compatible bucket via config.s3.*), and its own auth store — it does not share hoziron.db, auth.db, or the audit chain with the API-serving hoziron-server instance(s):
Cross-registry auth is per-registry token: the catalog client reads auth_token_env per configured registry entry and sends it as a Bearer header — there is no shared identity between the API-serving instance and a split-out registry beyond that token.
Related:
- system-overview.md — the surfaces model and crate tiers this topology deploys
- object-model.md, data-flow.md
- ../internals/catalog-packages.md — multi-registry configuration in detail
docs/decisions/024-unified-http-middleware-and-registry-auth.md