Self-hosting
Sizing
Section titled “Sizing”There is no hardware-to-agent-count guarantee. Start with an isolated test deployment, replay representative event rates and retention, exercise the dashboard queries and administrative actions you use, and size the brain, Postgres, and disk from the resulting CPU, memory, latency, and storage growth. Repeat the benchmark when event shape, retention, or replica topology changes.
PostgreSQL is authoritative for application rows, but the brain is not fully
stateless. Its persistent $Z4J_HOME volume (/data in the packaged
containers) can carry generated secrets, installation and restore-recovery
state, allowed-host configuration, and embedded PKI. Back up that state with
PostgreSQL, or preserve the corresponding external secret-manager values when
the files are not local. Multi-replica deployments rely on the load
balancer's session-affinity setting to pin each agent's WebSocket to one brain
pod. z4j provides no affinity-balance helper of its own.
Networking
Section titled “Networking”- Inbound to brain - TCP 7700 (HTTP + WebSocket) from the internet or private network.
- Outbound from agent - agents initiate WebSocket connections to z4j. Agents need DNS + TCP 443 (if you TLS-terminate at a reverse proxy).
- Brain to Postgres - usual asyncpg connection.
z4j does not need to reach agents. Agents sit behind NAT / private networks freely.
Terminate TLS at a reverse proxy (nginx / Caddy / Cloudflare). z4j speaks plain HTTP internally; X-Forwarded-Proto is respected.
Sample Caddy config:
z4j.example.com { reverse_proxy localhost:7700 { header_up X-Forwarded-Proto https }}Database
Section titled “Database”- Minimum: PostgreSQL 17. Recommended: PostgreSQL 18, which is what the shipped Compose file runs.
- Extensions: none required (no pgvector, no timescale in v1).
- Connection pool defaults:
pool_size=20,max_overflow=10per engine. Size them withZ4J_DATABASE_POOL_SIZEandZ4J_DATABASE_MAX_OVERFLOW; recycle and statement-cache tuning are exposed viaZ4J_DATABASE_MAX_INACTIVE_CONNECTION_LIFETIME_SECONDSandZ4J_DATABASE_STATEMENT_CACHE_SIZE(see brain memory tuning).
Secrets
Section titled “Secrets”For pip / SQLite installs z4j auto-mints secrets on first boot and persists them to ~/.z4j/secret.env. You don't need to set anything; restarts pick up the same values from disk.
For Docker / Postgres production deployments, generate explicit secrets so they survive container replacement and live alongside your other infra config:
Z4J_SECRET=$(openssl rand -hex 32)Z4J_SESSION_SECRET=$(openssl rand -hex 32)Z4J_AUDIT_CHAIN_SECRET=$(openssl rand -hex 32)Z4J_SECRET is the master from which the per-project HMAC signing secret is derived (deterministic - z4j re-derives it on every frame and never stores it). Z4J_SESSION_SECRET is independent and signs the user-session cookie. Z4J_AUDIT_CHAIN_SECRET independently signs the audit chain and is required outside exact dev mode.
The three keys have different rotation ceremonies:
- Session key: deploy the new
Z4J_SESSION_SECRETwith the old value inZ4J_PREVIOUS_SESSION_SECRETS, then restart every brain. Without that carry-over window, rotation invalidates every active browser session. - Master key: deploy the new
Z4J_SECRETwith the old value inZ4J_PREVIOUS_SECRETS, restart every brain, and re-credential every agent. The previous window keeps old bearer tokens and stored TOTP values readable, but frame-signing keys come from the current master alone. Existing API keys and outstanding invitation or password-reset links also stop working. This rotation does not touch the dedicated audit chain. - Audit-chain key: stop every brain replica first. For explicit secrets,
configure the new
Z4J_AUDIT_CHAIN_SECRETand put the old value inZ4J_AUDIT_CHAIN_PREVIOUS_SECRETS, runz4j audit rotate-chain-keyfrom an administrative environment using that same database and keyring, then start every brain and runz4j audit verify. For a managed SQLite install, usez4j audit rotate-chain-key --begin-managedwhile the brain is stopped. Changing the environment and restarting without the explicit transition is not a completed audit-key rotation.
See incident response for the complete ceremonies and the separate retirement clocks for previous keys.
Backups
Section titled “Backups”- Postgres: standard
pg_dumpor managed service snapshots. See backups guide. - Secrets: store in your secret manager (Vault, AWS Secrets Manager, SOPS).
Monitoring
Section titled “Monitoring”z4j exposes /metrics in Prometheus format (auth-gated; set Z4J_METRICS_AUTH_TOKEN or rely on the auto-minted token in ~/.z4j/secret.env). See operations § monitoring.