Deployment
Tares 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 taresd and one data volume.
Do not run multiple replicas against the same data.
Local
Install the package and run tares up:
uv tool install tares # or: pipx install tares
tares up # console on http://127.0.0.1:8787, data in ~/.tarestares up accepts --host, --port, --data-dir, --open, and --auth (see
Authentication, off by default). To expose the MCP endpoint for external agents,
also run tares mcp (see Connecting agents).
Docker
The published image runs the daemon by default:
docker run -p 8787:8787 -v tares-data:/data \
ghcr.io/glassflow/tares: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: tares
services:
taresd:
image: ghcr.io/glassflow/tares:${TARES_VERSION:-latest}
command: ["tares", "up", "--host", "0.0.0.0", "--data-dir", "/data"]
environment:
TARES_AUTH_TOKEN: "${TARES_AUTH_TOKEN:?set TARES_AUTH_TOKEN}"
volumes: [tares-data:/data]
expose: ["8787"]
restart: unless-stopped
mcp:
image: ghcr.io/glassflow/tares:${TARES_VERSION:-latest}
command: ["tares", "mcp", "--transport", "streamable-http", "--host", "0.0.0.0",
"--port", "8788", "--taresd", "http://taresd:8787"]
environment:
TARES_AUTH_TOKEN: "${TARES_AUTH_TOKEN:?set TARES_AUTH_TOKEN}"
expose: ["8788"]
depends_on: [taresd]
restart: unless-stopped
caddy:
image: caddy:2
ports: ["80:80", "443:443"]
environment:
TARES_DOMAIN: "${TARES_DOMAIN:-:80}"
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "caddy-data:/data"]
depends_on: [taresd, mcp]
restart: unless-stopped
volumes:
tares-data:
caddy-data:# Caddyfile: one hostname; /mcp and /sse go to the MCP server, the rest to the daemon
{$TARES_DOMAIN} {
handle /mcp* { reverse_proxy mcp:8788 }
handle /sse* { reverse_proxy mcp:8788 }
handle { reverse_proxy taresd:8787 }
}Set the domain and token, then start it:
export TARES_AUTH_TOKEN=$(openssl rand -hex 24)
TARES_DOMAIN=tares.example.com docker compose up -dA real TARES_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/tares: :latest (the default branch) and a tag per
release (:0.1.0, :0.1). Pin TARES_VERSION for reproducible deploys.
Configuration
taresd is configured by environment variables.
| variable | default | description |
|---|---|---|
TARES_DB | tares.duckdb | path to the DuckDB file (tares up uses ~/.tares) |
TARES_HOST | 127.0.0.1 | bind address (0.0.0.0 to expose) |
TARES_PORT | 8787 | bind port |
TARES_CATALOG | catalog.yaml | catalog YAML imported on first boot if the DB is empty |
TARES_CATALOG_SYNC | unset | if set, re-import the catalog YAML on every boot (file is source of truth) |
TARES_AUTH_TOKEN | unset | require this bearer token on the API, console, MCP, and ingest (what tares up --auth sets) |
TARES_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 Tares agents (or set one in the console) |
TARES_AGENT_MODEL | claude-sonnet-4-6 | model used by the Ask assistant and Tares agents |
The MCP server (tares mcp / tares-mcp) reads TARESD_URL, TARES_AUTH_TOKEN, and
TARES_MCP_TRANSPORT / TARES_MCP_HOST / TARES_MCP_PORT.
Authentication
Auth is one switch, set at launch:
tares up: open. No login; the API, console, and ingest are all reachable without a credential. The local default.tares 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 settingTARES_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.
Upgrading from 0.x
1.0 renamed the project from NavFlow to Tares. There is no compatibility layer: the old names are not read at all. Two of the renames would otherwise fail silently, so the daemon refuses to start instead of guessing.
Environment variables. Every NAVFLOW_* variable is now TARES_*, with the same meaning.
Nothing falls back to the old name. If any NAVFLOW_* variable is still set, tares up and
taresd exit and print the exact renames you need:
tares 1.0 renamed every NAVFLOW_* environment variable to TARES_*, and does NOT read the old names.
Still set in this environment:
NAVFLOW_DB -> TARES_DB
Rename them and start again.Three variables are gone rather than renamed: NAVFLOW_INGEST_TOKEN and NAVFLOW_READONLY were
folded into --auth plus scoped API keys in 0.2.0, and NAVFLOW_ANTHROPIC_KEY became
the standard ANTHROPIC_API_KEY.
The database file. navflow.duckdb is now tares.duckdb. DuckDB creates a missing file
happily, so an upgrade that left the old file in place would come up healthy and completely empty
with your data untouched beside it. Instead the daemon stops:
found a pre-1.0 database at ~/.tares/navflow.duckdb, and none at ~/.tares/tares.duckdb.Migrating is one move, with the daemon stopped:
mv ~/.tares/navflow.duckdb ~/.tares/tares.duckdb # or /data/… in a containerThe data directory itself also moved, from ~/.navflow to ~/.tares. If you never set
--data-dir or TARES_HOME, move the whole directory (mv ~/.navflow ~/.tares) before
renaming the file inside it.
Everything else is a straight rename, with no runtime guard: the package and command
(navflow to tares), the daemon (navflowd to taresd), the MCP proxy (navflow-mcp to
tares-mcp), the --navflowd flag (--taresd), the image (ghcr.io/glassflow/navflow to
ghcr.io/glassflow/tares), and the Claude Code plugin (navflow@navflow to tares@tares).
Backups
The data is the DuckDB file in the volume. Back it up by snapshotting the volume (or the host) or
copying /data/tares.duckdb; restore by putting the file back before start.