Deployment
NavFlow is a single process writing to a single DuckDB file. There is no external database or broker
to run. DuckDB is single-writer, so a deployment is exactly one navflowd and one data volume —
do not run multiple replicas against the same data.
Local
Install the package and run navflow up:
uv tool install navflow # or: pipx install navflow
navflow up # console on http://127.0.0.1:8787, data in ~/.navflownavflow up accepts --host, --port, --data-dir, --open, and --auth (see
Authentication — off by default). To expose the MCP endpoint for external agents,
also run navflow mcp (see Connecting agents).
Docker
The published image runs the daemon by default:
docker run -p 8787:8787 -v navflow-data:/data \
ghcr.io/glassflow/navflow:latestSelf-hosted (compose)
For a server, run the daemon, the MCP server, and a reverse proxy that terminates TLS and routes one hostname. The image serves both processes (same image, different command):
# docker-compose.yml
name: navflow
services:
navflowd:
image: ghcr.io/glassflow/navflow:${NAVFLOW_VERSION:-latest}
command: ["navflow", "up", "--host", "0.0.0.0", "--data-dir", "/data"]
environment:
NAVFLOW_AUTH_TOKEN: "${NAVFLOW_AUTH_TOKEN:?set NAVFLOW_AUTH_TOKEN}"
volumes: [navflow-data:/data]
expose: ["8787"]
restart: unless-stopped
mcp:
image: ghcr.io/glassflow/navflow:${NAVFLOW_VERSION:-latest}
command: ["navflow", "mcp", "--transport", "streamable-http", "--host", "0.0.0.0",
"--port", "8788", "--navflowd", "http://navflowd:8787"]
environment:
NAVFLOW_AUTH_TOKEN: "${NAVFLOW_AUTH_TOKEN:?set NAVFLOW_AUTH_TOKEN}"
expose: ["8788"]
depends_on: [navflowd]
restart: unless-stopped
caddy:
image: caddy:2
ports: ["80:80", "443:443"]
environment:
NAVFLOW_DOMAIN: "${NAVFLOW_DOMAIN:-:80}"
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "caddy-data:/data"]
depends_on: [navflowd, mcp]
restart: unless-stopped
volumes:
navflow-data:
caddy-data:# Caddyfile — one hostname; /mcp and /sse go to the MCP server, the rest to the daemon
{$NAVFLOW_DOMAIN} {
handle /mcp* { reverse_proxy mcp:8788 }
handle /sse* { reverse_proxy mcp:8788 }
handle { reverse_proxy navflowd:8787 }
}Set the domain and token, then start it:
export NAVFLOW_AUTH_TOKEN=$(openssl rand -hex 24)
NAVFLOW_DOMAIN=navflow.example.com docker compose up -dA real NAVFLOW_DOMAIN enables Caddy’s automatic HTTPS (point its DNS A record at the host first);
unset, it serves plain HTTP on :80. The console is at https://<domain>; the MCP endpoint at
https://<domain>/mcp.
Image versions
Images are published to ghcr.io/glassflow/navflow: :latest (the default branch) and a tag per
release (:0.1.0, :0.1). Pin NAVFLOW_VERSION for reproducible deploys.
Configuration
navflowd is configured by environment variables.
| variable | default | description |
|---|---|---|
NAVFLOW_DB | navflow.duckdb | path to the DuckDB file (navflow up uses ~/.navflow) |
NAVFLOW_HOST | 127.0.0.1 | bind address (0.0.0.0 to expose) |
NAVFLOW_PORT | 8787 | bind port |
NAVFLOW_CATALOG | catalog.yaml | catalog YAML imported on first boot if the DB is empty |
NAVFLOW_CATALOG_SYNC | unset | if set, re-import the catalog YAML on every boot (file is source of truth) |
NAVFLOW_AUTH_TOKEN | unset | require this bearer token on the API, console, MCP, and ingest (what navflow up --auth sets) |
NAVFLOW_OTLP_GRPC_PORT | 4317 | OTLP/gRPC receiver port (off to disable; needs the otlp-grpc extra) |
ANTHROPIC_API_KEY | unset | model key for the Ask assistant and NavFlow agents (or set one in the console) |
NAVFLOW_AGENT_MODEL | claude-sonnet-4-6 | model used by the Ask assistant and NavFlow agents |
The MCP server (navflow mcp / navflow-mcp) reads NAVFLOWD_URL, NAVFLOW_AUTH_TOKEN, and
NAVFLOW_MCP_TRANSPORT / NAVFLOW_MCP_HOST / NAVFLOW_MCP_PORT.
Authentication
Auth is one switch, set at launch:
navflow up— open. No login; the API, console, and ingest are all reachable without a credential. The local default.navflow up --auth— secured. Every route requires a credential: the console and API, and ingest too. A bare--authgenerates a root token, persists it to the data dir, and prints a click-to-login URL each launch (…/?token=<root>).--auth=<token>(or settingNAVFLOW_AUTH_TOKEN) uses your own token — the shape for hosted/scripted deploys.
There is no separate ingest token and no read-only mode. On a secured instance you hand machines their own scoped API keys — never the root token.
API keys
The root token is the operator’s login; you mint narrower credentials from it. In the console → Security → API keys, create a key with a subset of three scopes:
| scope | grants |
|---|---|
read | queries, timelines, catalog, an agent’s own views & subscriptions (the MCP read surface) |
ingest | POST /ingest/*, /v1/*, write memories |
admin | sources / views / triggers, key management (implies the others) |
Keys are shown once at creation, revocable, and never returned again. Typical holders: an MCP agent
→ read; a producer (Vercel drain, OTLP exporter, webhook) → its own ingest key; the Claude Code
plugin → read + ingest. On a secured instance, creating a push source mints an ingest key
for it and shows it once — hand that to the producer as Authorization: Bearer ….
The root token is the console login. For per-user SSO, put a proxy (oauth2-proxy, Tailscale, Caddy basic-auth) in front of the console; machines keep their scoped API keys.
Backups
The data is the DuckDB file in the volume. Back it up by snapshotting the volume (or the host) or
copying /data/navflow.duckdb; restore by putting the file back before start.