Skip to content

Self-hosting

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.

  • 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
}
}
  • 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=10 per engine. Size them with Z4J_DATABASE_POOL_SIZE and Z4J_DATABASE_MAX_OVERFLOW; recycle and statement-cache tuning are exposed via Z4J_DATABASE_MAX_INACTIVE_CONNECTION_LIFETIME_SECONDS and Z4J_DATABASE_STATEMENT_CACHE_SIZE (see brain memory tuning).

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:

Terminal window
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_SECRET with the old value in Z4J_PREVIOUS_SESSION_SECRETS, then restart every brain. Without that carry-over window, rotation invalidates every active browser session.
  • Master key: deploy the new Z4J_SECRET with the old value in Z4J_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_SECRET and put the old value in Z4J_AUDIT_CHAIN_PREVIOUS_SECRETS, run z4j audit rotate-chain-key from an administrative environment using that same database and keyring, then start every brain and run z4j audit verify. For a managed SQLite install, use z4j audit rotate-chain-key --begin-managed while 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.

  • Postgres: standard pg_dump or managed service snapshots. See backups guide.
  • Secrets: store in your secret manager (Vault, AWS Secrets Manager, SOPS).

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.