Skip to content
kazma.
ع Star 7 Get Started

Deployment

Production deployment paths for Kazma: Docker Compose (primary), Kubernetes (Hub service), Windows native, and server management. Honest notes on what each artifact actually deploys.


TargetWhat it deploysStatus
Docker Compose (docker-compose.yml + Dockerfile)The main Kazma agent + Web UI (uvicorn).✅ Primary, production-ready.
Windows native (setup.ps1)Local dev venv bootstrap.✅ Active.
Kubernetes (kubernetes/)A separate Hub API service — not the main Kazma agent.⚠ See §4.
Cloudflare Pages / edge workers❌ Not applicable. Kazma is a Python/uvicorn server, not an edge deployment.
Bare uvicornThe main agent.kazma serve / kazma-web.

Honest note: Older tasking mentioned “Cloudflare Pages, serverless edge workers.” Kazma is a stateful Python service (LangGraph + SQLite + optional ChromaDB). It is not designed for serverless/edge deployment. The MCP tooling skills in this environment cover Cloudflare Workers, but they are unrelated to deploying Kazma itself.


Section titled “2. Docker Compose (recommended for production)”

The live file is Dockerfile at the repo root — do not copy-paste a snapshot here (it drifted before: missing git, Arabic fonts, LibreOffice, Tesseract, ClamAV, and document-platform). What it actually does:

  • Base: python:3.11-slim.
  • System deps: git, libpq, Noto Arabic fonts, LibreOffice, Tesseract (eng+ara), ClamAV, build-essential, curl.
  • Python: pip install -e ".[rag,postgres,document-platform]".
  • Runs as non-root kazma. Listens on container port 8000.
  • Entrypoint: scripts/docker-entrypoint.sh.

--host 0.0.0.0 is required inside the container so the published port reaches the service. Docker’s network isolation is the security boundary.

Live file: docker-compose.yml. Accurate facts (do not restore the old 8000:8000 / /root/.kazma snapshot):

  • Host port 9090 → container 8000 (HOST_PORT to override) so bookmarks match kazma serve.
  • Volumes: kazma_data/app/kazma-data; kazma_vectors/home/kazma/.kazma/vector_memory (the kazma user home, not /root).
  • KAZMA_VECTOR_PATH=/home/kazma/.kazma/vector_memory is set in compose.
  • Health check: curl -f http://localhost:8000/health/ready every 30 s, 300 s start period (cold start loads embeddings + MCP).
  • restart: unless-stopped survives host reboots (it does not restart on unhealthy).
Terminal window
cp .env.example .env
# Edit .env: set OPENAI_API_KEY, KAZMA_SECRET, any platform tokens
# Generate a strong secret:
# openssl rand -hex 32 # → put in KAZMA_SECRET
docker compose up -d --build
docker compose logs -f kazma

Verify:

Terminal window
curl -s http://localhost:9090/health/ready

Excludes archive/, __pycache__/, .venv/, .git/, tests/, kazma-data/, kubernetes/, docs/, *.md, .env, *.db, build caches — keeping the image lean and secrets out.


Terminal window
# Development / single-host — use kazma serve (not raw uvicorn on Windows)
pip install -e ".[rag,dev,tui]"
kazma serve # 127.0.0.1:9090

For a public-facing host behind a reverse proxy:

Terminal window
# ONLY with KAZMA_SECRET set does `kazma serve` bind 0.0.0.0
KAZMA_SECRET=$(openssl rand -hex 32) \
KAZMA_TRUSTED_PROXIES=127.0.0.1 \
kazma serve

Never expose 0.0.0.0 without KAZMA_SECRET. The HITL approval endpoint would otherwise be unauthenticated. Put Kazma behind nginx/Caddy/Traefik with TLS and let the proxy hold the public socket.

KAZMA_TRUSTED_PROXIES is required whenever a reverse proxy is in front of Kazma. Set it to the address the proxy connects from127.0.0.1 for a same-host nginx/Caddy, or the container/bridge IP in Docker.

Without it, request.client.host is the proxy for every request. A same-host proxy makes every internet visitor look like 127.0.0.1, which Kazma treats as the local operator and auto-issues an admin session to — a complete auth bypass over both HTTP and WebSocket (audit F-01, fixed 2026-08-29). With it set, Kazma reads the real client from X-Forwarded-For and stops trusting peer address as a credential.

Your proxy must set the forwarded headers, and must overwrite rather than append a client-supplied value. The shipped deploy/nginx-ha.conf already does:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

serve.py passes --proxy-headers --forwarded-allow-ips to uvicorn automatically from this variable. If you launch uvicorn yourself, pass them too.


4. Kubernetes (Hub service only — read carefully)

Section titled “4. Kubernetes (Hub service only — read carefully)”

The kubernetes/ directory contains two manifests:

  • kubernetes/hub-deployment.yaml — a Namespace, Deployment (3 replicas), Service, and Ingress for an image named kazma/hub-api:latest.
  • kubernetes/hub-secrets.yaml — a Secret with database-url (PostgreSQL) and redis-url.
hub-deployment.yaml
spec:
replicas: 3
template:
spec:
containers:
- name: hub-api
image: kazma/hub-api:latest
ports:
- containerPort: 8000
env:
- name: DATABASE_URL # PostgreSQL — NOT used by the main Kazma agent
valueFrom:
secretKeyRef: { name: hub-secrets, key: database-url }
- name: REDIS_URL # Redis — NOT used by the main Kazma agent
valueFrom:
secretKeyRef: { name: hub-secrets, key: redis-url }
livenessProbe:
httpGet: { path: /api/v1/health, port: 8000 }
  1. These deploy a Hub service, not the main Kazma agent. The image kazma/hub-api:latest is not built from this repo’s Dockerfile and is not published from here.
  2. The referenced infrastructure does not match the main codebase. The main Kazma agent uses SQLite (WAL) everywhere (ConfigStore, checkpointer, TaskStore, snapshots, vector memory via ChromaDB). It does not read DATABASE_URL or REDIS_URL, and has no PostgreSQL or Redis client. These env vars belong to a separate (aspirational or external) Hub API service.
  3. The health path /api/v1/health differs from the main app’s /health/live and /health/ready (kazma-ui/.../health.py:94,104).
  4. Resource limits (256Mi–512Mi, 250m–500m) are reasonable for a stateless API but too small for the main agent if ChromaDB + sentence-transformers are loaded (those alone can exceed 512 Mi).

Recommendation: treat kubernetes/ as a starting point for deploying a separate Hub API. To deploy the main Kazma agent on Kubernetes, write a new manifest using the repo’s Dockerfile, the /health/live probe, an explicit KAZMA_SECRET, a PVC for kazma-data/ and the vector path, and resource limits adequate for the RAG extras (≥1 Gi memory).


Cross-platform path policy and data layout: Portability.

setup.ps1 is the deterministic, fail-fast, idempotent Windows bootstrap (PowerShell 5.0+). It:

  1. Validates the environment (Python 3.11+, uv, kazma.yaml).
  2. Syncs the virtual environment from pyproject.toml.
  3. Runs a foundation integrity check (core imports + test collection).
Terminal window
.\setup.ps1
.\setup.ps1 -Debug # verbose

PowerShell rule (from AGENTS): never chain commands with && or ||. Use ; and check $LASTEXITCODE. The Bash tool in this environment uses Git Bash, not PowerShell.


On a host watched by KazmaAgent (the health-gated supervisor), pick up code with one command. Do not kill python/uvicorn by hand — that fights the guard (stale port holder or a 180s false “never ready”).

Terminal window
cd <kazma-install>
& '.venv\Scripts\python.exe' scripts\service\kazma_guard.py --reload

Wait for Kazma is up. build … (first boot can take a few minutes for imports / MCP / Postgres; budget 900s). --reload plants a flag before killing serve.py, so the long-lived guard does not treat that kill as a crash and climb the backoff ladder (5s → 300s). If --reload still sits on WinError 10061 for minutes, the guard process is still running old code — restart KazmaAgent once (schtasks /End then /Run KazmaAgent on Windows). --status shows whether the watcher and /health/ready agree. --install registers the OS task (install_service.py).

On Windows, start via kazma serve or the guard — not python -m uvicorn. Uvicorn 0.36+ hardcodes ProactorEventLoop, and psycopg-async then cannot open the Postgres checkpointer.


EndpointPurposeLocation
GET /health/liveLivenesshealth.py:94
GET /health/readyReadinesshealth.py:104
GET /health/detailsDetailed healthhealth.py:148
GET /api/gateway/statusGateway/adapter status (used by Docker healthcheck + kazma status)gateway router

  • KAZMA_SECRET set (strong random) — required to protect /api/approve.
  • Server bound to 127.0.0.1 (or behind a TLS-terminating reverse proxy).
  • KAZMA_TRUSTED_PROXIES set to the proxy’s address whenever a reverse proxy is in front — otherwise every visitor is treated as the local operator (audit F-01). Verify with curl -s https://your-host/api/auth/status: authenticated must be false before login.
  • Provider API keys rotated if this instance ever served /api/settings on a build before 2026-08-29 (audit F-02 leaked them in plaintext).
  • Volumes persisted for kazma-data/ and the vector memory path.
  • kazma.yaml safety.hitl.enabled: true and a complete require_approval_for list.
  • All three HITL build sites pass hitl_config (default builds do; verify any custom build).
  • MCP stdio servers sandboxed (no auth on stdio transport).
  • Skills signed (kazma hub sign) with the same KAZMA_SECRET used at load time.
  • Resource limits account for ChromaDB + sentence-transformers if RAG is enabled (≥1 Gi).
  • Health check wired (/api/gateway/status or /health/live).
  • Logs shipping to your collector (JSON format available via logging.format: json).

9. Resource considerations (24 GB VRAM setups)

Section titled “9. Resource considerations (24 GB VRAM setups)”

The repo’s notes mention “resource constraints on 24 GB VRAM setups.” Practical guidance:

  • sentence-transformers (BAAI/bge-m3) is CPU-friendly (~2.2 GB) — it does not need a GPU. VRAM is only relevant if you point Kazma at a local GPU model server (Ollama/LM Studio/vLLM).
  • For local LLM inference, the model server (not Kazma) owns the VRAM budget. Kazma itself is a lightweight httpx client to that server.
  • ChromaDB is memory-mapped; size the vector volume accordingly.

Kazma pushes a status update to every configured platform (Telegram/Discord/Slack) when the server starts, restarts, shuts down, or fails to boot — so you can tell from chat when something went wrong (a hung boot, a crash that emits no shutdown message, a bad bot token).

EventIconWhenWhat it tells you
starting🔵Top of startup (before MCP)Boot began. If you see this with no started, the boot hung or crashed.
started🟢End of startup (all subsystems up)Server is healthy. Includes Adapters: + Model: detail.
restarted🔄Auto-upgraded from startedShutdown→start within the restart window — intentional restart, not crash-recovery.
shutting_down🟡Top of graceful shutdownClean stop. Its absence means a crash / kill -9.
startup_failed🔴Gateway-start failure guardBoot error (bad token, network) — the error is in the message body.

Notifications route through the SwarmMessageBus — the same bus that delivers swarm worker output. No separate notification path is constructed. The bus is wired during KazmaAppBuilder.build() (before the lifespan), and FanOutBusAdapter fans out to every configured platform. When no platform bus is configured (NullBusAdapter), the feature self-disables silently.

notifications:
lifecycle:
enabled: true
events: [starting, started, shutting_down, startup_failed]
restart_window_seconds: 60 # 0 disables restart detection

Config is live-re-read on every boot/shutdown — toggle it via the Settings API or kazma.yaml without a restart for the next boot.

The messages need a destination. Set connectors.<platform>.swarm_chat_id to the chat ID where you want them delivered (typically your DM with the bot):

Terminal window
# Telegram example — set to your user ID (same as allowed_users)
# Via the Settings API:
curl -X PUT http://127.0.0.1:9090/api/settings/single \
-H "Content-Type: application/json" \
-H "X-Kazma-Secret: $KAZMA_SECRET" \
-d '{"key":"connectors.telegram.swarm_chat_id","value":"<your-chat-id>"}'

Without swarm_chat_id, the bus stays NullBusAdapter and notifications are dropped silently.

See: Configuration → notifications for the full key reference.


  • kubernetes/ deploys a Hub API, not the main agent, and references PostgreSQL + Redis, which the main codebase does not use. Flagged prominently to prevent misdeployment.
  • Volume path mismatch (/root/.kazma/... vs the kazma user’s home) in docker-compose.yml — set KAZMA_VECTOR_PATH explicitly to be safe.
  • No Cloudflare/edge deployment path. Kazma is a stateful Python service; don’t attempt serverless packaging.
  • Health path differs between the K8s manifest (/api/v1/health) and the real app (/health/live).