Skip to main content

Install z4j.

Three deployment paths, one platform. Every tier runs the same dashboard, the same six engines and the same audit surface. Postgres is what adds replicas, full-text search and partitioned history.

Lightest

Pip (SQLite)

Pure Python. No container runtime required.

Best for

Solo developers, homelab, local dev, bare-metal servers, CI

Homelab Solo developer
Stack
PyPI package Python 3.11+ SQLite (local file)
Default

Docker (SQLite)

One container. SQLite bundled in the image. No env vars required.

Best for

Homelab, small teams, evaluation, proof-of-concept

Homelab Small team Evaluation
Stack
Docker image SQLite (bundled)
Production

Docker (Postgres)

Same image, external Postgres. Horizontal-scale ready.

Best for

Teams that already operate Postgres and want central backups, replicas, and retained history

Small business Medium business Platform teams
Stack
Docker image PostgreSQL 17+
Side by side

Compare the three paths

Capability Pip (SQLite)Docker (SQLite)Docker (Postgres)
Container runtime required None (pure Python) Docker Docker + Postgres
Database SQLite SQLite (persisted volume) PostgreSQL 17+
Services to run 1 process 1 container 2 containers
Topology Single host Single container Postgres-backed replicas
Capacity guidance Benchmark workload Benchmark workload Benchmark workload
Horizontal scaling
Full-text search
Range-partitioned events
Auto-migrations
Auto-generated secrets
Central backup and point-in-time recovery
Lightest

Pip (SQLite)

Pure Python. No container runtime required.

Install command

bash
# Install z4j + umbrella (dashboard, backend, SQLite, all bundled)
pip install z4j

# Start it. First boot auto-mints HMAC secrets, runs migrations, and prints
# a one-time setup URL to stderr. Open the URL to create the first admin.
# z4j auto-detects the server's hostname, FQDN, and LAN IPs for Host-header
# validation. Dev mode still binds to loopback and refuses a LAN bind.
z4j serve

# Open http://localhost:7700 on that host, or use an SSH port forward.

# For a public reverse proxy, configure production explicitly. The persistent
# allowed-hosts file is only for local SQLite names when the env var is unset.
export Z4J_ENVIRONMENT=production
export Z4J_PUBLIC_URL=https://tasks.example.com
export Z4J_ALLOWED_HOSTS='["tasks.example.com"]'
z4j serve

# Useful CLI commands:
z4j check               # config + DB reachable (does NOT verify schema head)
z4j migrate current --check-heads   # exits non-zero unless the schema IS at head
z4j status              # version, DB URL, user/project/agent counts
z4j createsuperuser     # provision an admin without the setup URL
z4j changepassword      # reset a user password
z4j migrate upgrade head    # run alembic migrations explicitly
z4j audit verify        # verify the HMAC-chained audit log
z4j reset-setup --force # clear pending pre-first-boot tokens; restart z4j serve to mint a new URL

What runs

1

One Python process

z4j serves the FastAPI API, the WebSocket agent gateway, and the React dashboard from a single process. SQLite lives on local disk.

Requirements

  • Python 3.11 or newer
  • No Docker, no Redis, no broker required
  • Writes to ~/.z4j/z4j.db (SQLite)
Use this when
  • Running z4j on a Raspberry Pi or homelab NUC
  • Evaluating z4j locally before committing to a container
  • CI jobs that need a task dashboard ephemerally
  • Air-gapped Python environments where Docker is not permitted
Database
SQLite (auto-created at ~/.z4j/z4j.db)
Scale envelope
Single-host evaluation or development; benchmark before production
Default

Docker (SQLite)

One container. SQLite bundled in the image. No env vars required.

Install command

bash
# The default. One file. SQLite bundled in the image.
git clone https://github.com/z4jdev/z4j.git && cd z4j
docker compose up -d

# Tail logs for the first-boot admin setup URL.
docker compose logs -f z4j

# Or skip interactive setup with bootstrap env vars in .env:
#   [email protected]
#   Z4J_BOOTSTRAP_ADMIN_PASSWORD=<long random>

# Add Caddy auto-HTTPS on top after putting Z4J_DOMAIN and
# Z4J_ACME_EMAIL in a root .env file:
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d

What runs

1

z4j

z4jdev/z4j:latest

One image. Bundles the FastAPI backend, the React dashboard, and the SQLite driver. Auto-generates secrets on first boot, persists them to the z4j_data volume, auto-runs migrations. Exposes port 7700.

Requirements

  • Docker 20.10 or newer
  • Port 7700 bound to localhost by default (reverse-proxy for public access)
  • Persistent volume for SQLite database and persisted secrets
Use this when
  • First-time evaluation: clone the repo, docker compose up, done
  • Internal tools for a team of 2 to 20 developers
  • Homelab Docker Compose stacks (Synology, Unraid, TrueNAS)
  • Customer demos and sales engineering POCs
  • Single-instance production where ops simplicity wins over scale
Database
SQLite, stored in the z4j_data named volume
Scale envelope
Single-container deployment; capacity depends on workload and host
Production

Docker (Postgres)

Same image, external Postgres. Horizontal-scale ready.

Install command

bash
# Two services. Same z4jdev/z4j image as the default compose. The shipped
# file selects Postgres with structured Z4J_DATABASE_* values. No build required.

git clone https://github.com/z4jdev/z4j.git && cd z4j
umask 077
cat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 48)
Z4J_SECRET=$(openssl rand -hex 48)
Z4J_SESSION_SECRET=$(openssl rand -hex 48)
Z4J_AUDIT_CHAIN_SECRET=$(openssl rand -hex 48)
Z4J_PUBLIC_URL=https://z4j.yourdomain.com
Z4J_ALLOWED_HOSTS=["z4j.yourdomain.com"]
EOF

docker compose -f docker-compose.postgres.yml up -d

# Capture the first-boot setup URL from z4j logs:
docker compose -f docker-compose.postgres.yml logs -f z4j

# Or skip interactive setup entirely with bootstrap env vars in .env:
#   [email protected]
#   Z4J_BOOTSTRAP_ADMIN_PASSWORD=<long random>

# Layer Caddy auto-HTTPS on top:
docker compose -f docker-compose.postgres.yml -f docker-compose.caddy.yml up -d

What runs

1

z4j

z4jdev/z4j:latest

Same image as the default. Bundles backend plus dashboard. The shipped Compose file supplies structured Z4J_DATABASE_* connection fields.

2

z4j-postgres

postgres:18.6 (digest-pinned)

Your primary datastore. Holds events, tasks, schedules, users, HMAC-chained audit log, and partitioned event history.

Requirements

  • Docker Compose v2+ or Kubernetes
  • PostgreSQL 17 or newer (the shipped Compose file uses PostgreSQL 18)
  • Reverse proxy with TLS (Caddy, nginx, Traefik) or cloud load balancer
  • Secrets management (env, Vault, Sealed Secrets, etc.)
Use this when
  • Self-hosted production deployments with audit requirements
  • Central Postgres with point-in-time recovery already in place
  • Teams with dedicated infrastructure or platform engineering
  • Environments where the database has to be a managed Postgres your platform team already backs up and restores
  • Kubernetes stacks (z4j ships no Helm chart for the brain; a plain Deployment + Service + Ingress works)
Database
PostgreSQL 17+ (the shipped Compose file uses PostgreSQL 18)
Scale envelope
Multiple brain replicas behind a load balancer; benchmark the intended workload
HTTPS setup

Reverse proxy, your choice.

z4j does not bundle a reverse proxy. Pick the TLS pattern that matches your existing infra.

Next step

Install the agent in your app

After z4j is running, drop the agent package into your Django, Flask, or FastAPI app. One pip install plus three environment variables.

Pick your engine

Every tier supports every engine

Install one or more engine adapters alongside your framework. Every deployment tier supports all six.

Architecture FAQ

One image, one clear story.

Answers to the common confusions about z4j's container layout.

Is the frontend a separate container?

No. z4j image bundles the FastAPI backend and the compiled React dashboard together. The backend serves the dashboard as static HTML, CSS, and JS from the same process. There is no second container for the UI.

Is there a separate image for each tier?

No. Both Docker tiers use the same z4jdev/z4j image. The runtime picks SQLite or Postgres based on the Z4J_DATABASE_URL environment variable.

Then what are the image tags for?

Tags pin versions, not modes. Use :latest for the most recent release, or pin the immutable release tag from the release notes for reproducible deploys. Multi-arch images are published for linux/amd64 and linux/arm64.

How many services for z4j + Postgres?

Two. z4j runs the backend and dashboard from one image. z4j-postgres runs Postgres. No separate frontend.

Does z4j need Redis or RabbitMQ?

No. z4j communicates with your agents over a direct WebSocket. Your task queue engine uses whatever broker it already uses (Redis, RabbitMQ, SQS). z4j observes, it does not re-broker.

Can I roll back to the previous image tag?

Not by bumping the tag down. Once the current schema has run and the database holds z4j-native schedules, the downgrade is refused rather than allowed to drop state the older schema cannot represent. Take a backup before you upgrade, and restore that backup if you need to go back.

Can I switch from SQLite to Postgres later?

You can switch backends, but changing Z4J_DATABASE_URL does not copy data. It starts a new Postgres installation while your users, audit chain, and schedules remain in SQLite. Keep the SQLite volume and use the API or audit export for records you must retain.

Still picking a tier?

Solo developer or homelab: start with pip. Team of 2-20: default z4j compose. Anyone who already runs Postgres, or who needs central backups and replicas: z4j + Postgres.