Recipe — Single-node prod¶
Ladder position: this recipe combines rung (b) — Secured single node and rung (c) — Durable single node of the supported deployment configurations guide. The ladder has the complete
.envfor each rung (JWT identity, brain enforcement,STATE_DB_URIstate externalization) — this page is the docker-compose walkthrough.
One host, durable, no swarm. Good for a small team or a staging box: a durable
Postgres/pg-age KG, the REST gateway, a core slice of the *-mcp fleet, and
optional Langfuse/OpenBao — all via docker compose on a single machine.
What runs¶
| Component | How |
|---|---|
REST gateway (python -m agent_utilities, :9000 via HOST/PORT) |
container or host process; hosts the KG daemon (flock-elected) and serves /api/graph/*, /api/fleet/*, /metrics |
| KG host daemon | inside the gateway process; headless alternative: graph-os-daemon (no HTTP) |
| Knowledge graph | tiered (or fanout) with a durable Postgres tier (GRAPH_DB_URI) — the Postgres image must carry Apache AGE + pgvector + ParadeDB (see note below) |
| Durable platform state | sessions/goals/checkpoints/task queue on the same Postgres (STATE_DB_URI) |
Core *-mcp connectors |
the single-node-prod profile from mcp-fleet.registry.yml (openbao, technitium, container-manager, vector, caddy, …) |
| Caddy | HTTPS reverse proxy in front of the gateway + connectors |
| OpenBao | optional secrets store |
| Langfuse | optional observability |
| Kafka / Keycloak / swarm | not in this tier (see Enterprise) |
Postgres extension requirement. The durable tier must be a Postgres that carries Apache AGE (
age, native openCypher — thebackend: "age"path), pgvector (vector), and ParadeDB (pg_search), withageandpg_searchinshared_preload_libraries. The curatedregistry.arpa/pg-ageimage (services/pg-age/, builtFROM paradedb/paradedbPG18 + AGE 1.7.0) bundles all three. The stockparadedb/paradedbimage has pgvector + pg_search but NOT AGE — using it leaves Postgres on the bounded regex transpiler (cypher_support="subset"). See Graph Backend Architecture → Extension Dependencies.
Steps¶
# 1. Bring up Postgres/pg-age (publishes host port 5433, db agent_kg,
# user/password agent/agent)
docker compose -f docker/pg-age.compose.yml up -d
docker exec agent-pg-age psql -U agent -d agent_kg -c 'CREATE DATABASE agent_state'
# 2. Start the REST gateway pointed at it (also hosts the KG daemon)
export GRAPH_BACKEND=tiered
export GRAPH_DB_URI=postgresql://agent:agent@localhost:5433/agent_kg
export STATE_DB_URI=postgresql://agent:agent@localhost:5433/agent_state
python -m agent_utilities # REST API on :9000 (HOST/PORT)
# Headless alternative (no REST surface): uv run graph-os-daemon
# 3. Deploy the core connector slice (single-node-prod profile)
# Build/run each from its docker/compose.yml, or use portainer-sync-agent.
.env (generalized)¶
GRAPH_BACKEND=tiered
GRAPH_DB_URI=postgresql://agent:REDACTED@localhost:5433/agent_kg
# Durable platform state: sessions/goals, durable-exec checkpoints, and the
# KG task queue all move onto Postgres; the task queue auto-resolves to
# `postgres` when this is set (rung c of the ladder)
STATE_DB_URI=postgresql://agent:REDACTED@localhost:5433/agent_state
KG_DAEMON_ROLE=host # this process owns the single KG daemon
# Identity & enforcement (rung b of the ladder — see the guide for details)
KG_AUTH_REQUIRED=1
AUTH_JWT_JWKS_URI=https://idp.example.internal/realms/agents/protocol/openid-connect/certs
AUTH_JWT_ISSUER=https://idp.example.internal/realms/agents
KG_BRAIN_ENFORCE=1
# Optional — secrets
SECRETS_VAULT_URL=http://localhost:8200
VAULT_AUTH_METHOD=token
# Optional — observability
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_PUBLIC_KEY=lf_pk_REDACTED
LANGFUSE_SECRET_KEY=lf_sk_REDACTED
# Model provider
OPENAI_API_KEY=sk-REDACTED
Connectors¶
Deploy the connectors tagged single-node-prod in
deploy/mcp-fleet.registry.yml. Each maps its container port 8000 to a unique
host port (8200+) so they coexist on one machine. Front them with Caddy
(docker/caddy / the caddy-mcp connector) for TLS + routing.
Verify¶
curl -s -X POST localhost:9000/api/graph/query \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"cypher":"MATCH (n) RETURN count(n) AS n"}'
# Restart the gateway — KG state, sessions, and goals persist (Postgres now).
# Without a Bearer token the same call returns 401 (KG_AUTH_REQUIRED=1).
Graduate to enterprise¶
Add a swarm, Keycloak SSO, the Kafka event backbone, LGTM observability, and the
full connector fleet — see Enterprise, driven by the
agent-os-genesis (alias day0) skill-workflow. The flag-level path is rungs
(d) and (e) of the
deployment configurations ladder.