Skip to Content
TaresDeployment

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 ~/.tares

tares 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 as a second process:

tares mcp --transport streamable-http --port 8788 --taresd http://localhost:8787

The MCP endpoint is then at http://localhost:8788/mcp.

Docker

One image serves both processes: it runs the daemon by default, and the MCP server when started with the tares mcp command. Run them as two containers on a shared network so they can reach each other:

docker network create tares docker run -d --name taresd --network tares -p 8787:8787 -v tares-data:/data \ ghcr.io/glassflow/tares:latest docker run -d --name tares-mcp --network tares -p 8788:8788 \ ghcr.io/glassflow/tares:latest \ tares mcp --transport streamable-http --host 0.0.0.0 --port 8788 --taresd http://taresd:8787

The console is at http://localhost:8787, the MCP endpoint at http://localhost:8788/mcp. (Only want the console? The first two commands are enough.) Running both plus TLS is what the compose file below wires up for you.

To tear it down:

docker rm -f taresd tares-mcp docker network rm tares docker volume rm tares-data # only if you also want to delete the data

Server (compose + TLS)

For a server, run the daemon, the MCP server, and a reverse proxy that terminates TLS and routes one hostname. Two files in one directory: docker-compose.yml and the Caddyfile it mounts. Both ship in the repo under deploy/compose/, or copy them from here. 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:

Save this next to it as Caddyfile — the compose file mounts it into the Caddy container (./Caddyfile:/etc/caddy/Caddyfile), so compose fails to start without it:

# 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 from that directory:

export TARES_AUTH_TOKEN=$(openssl rand -hex 24) TARES_DOMAIN=tares.example.com docker compose up -d

A 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

Both processes are configured by environment variables. The read by column says which process uses each one: the daemon (taresd), the MCP server (tares-mcp), or both.

variableread bydefaultdescription
TARES_DBtaresdtares.duckdbpath to the DuckDB file (tares up uses ~/.tares)
TARES_HOSTtaresd127.0.0.1bind address (0.0.0.0 to expose)
TARES_PORTtaresd8787bind port
TARES_CATALOGtaresdcatalog.yamlcatalog YAML imported on first boot if the DB is empty
TARES_CATALOG_SYNCtaresdunsetif set, re-import the catalog YAML on every boot (file is source of truth)
TARES_AUTH_TOKENbothunsetrequire this bearer token everywhere (what tares up --auth sets); tares-mcp also sends it to the daemon
TARES_OTLP_GRPC_PORTtaresd4317OTLP/gRPC receiver port (off to disable; needs the otlp-grpc extra)
ANTHROPIC_API_KEYtaresdunsetmodel key for the Ask assistant and Tares agents (or set one in the console)
ANTHROPIC_AUTH_TOKENtaresdunseta bearer token instead of the key, for a gateway that wants Authorization: Bearer; beats the key when both are set
ANTHROPIC_BASE_URLtaresdhttps://api.anthropic.coman Anthropic-compatible gateway to send model calls through; TARES_ANTHROPIC_BASE is the old name, still read
TARES_AGENT_MODELtaresdclaude-sonnet-4-6model used by the Ask assistant and Tares agents
TARESD_URLtares-mcphttp://127.0.0.1:8787the daemon to proxy to (what --taresd sets)
TARES_MCP_TRANSPORTtares-mcpstdiostdio, streamable-http, or sse
TARES_MCP_HOSTtares-mcp127.0.0.1bind address for the HTTP transports
TARES_MCP_PORTtares-mcp8788bind port for the HTTP transports

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 --auth generates a root token, persists it to the data dir, and prints a click-to-login URL each launch (…/?token=<root>). --auth=<token> (or setting TARES_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 → Settings → API keys, create a key with a subset of three scopes:

scopegrants
readqueries, timelines, catalog, an agent’s own views & subscriptions (the MCP read surface)
ingestPOST /ingest/*, /v1/*, write memories
adminsources / views / triggers, key management (implies the others)

Keys are shown once at creation, revocable, and never returned again. Example key types:

holderscopes
an MCP agentread
a producer (Vercel drain, OTLP exporter, webhook)ingest
the Claude Code pluginread + 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 …. It appears in Settings → API keys like any other key, where you can mint new ones and deactivate existing ones.

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.

Settings

Settings in the console footer holds the instance’s access mode and credentials, in four tabs (/settings?tab=… deep-links each):

  • Access and API keys: the auth mode this instance was launched with, and the API keys.
  • Model access: what Tares agents and Ask run on. First the choice of where model calls go, Anthropic directly or through a gateway; then the key, or the gateway’s URL and token. Values stored here win over the environment (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL); with none stored, the environment’s values show as such. Credentials are never returned by the API.
  • GitHub: a GitHub token stored once, by name. Sources of the github connector reference it as credential: <name> instead of a token per source, and MCP servers reference it as their auth value credential:github/<name>. Test shows the login it belongs to; the row lists which sources and MCP servers use it; Rotate replaces the token in one place and every user of it follows on its next poll or connection. The token is never returned by the API and never included in a catalog export. Use a fine-grained token with Contents (read, or read and write on a repository an agent should update), Pull requests read and write, Metadata read.
  • Slack: the workspace bot token findings and firings are posted with, and the signing secret behind inbound Slack events.

Using an LLM gateway

Tares agents and Ask talk to api.anthropic.com by default. An organisation that routes all model traffic through a gateway (LiteLLM, Portkey, Helicone, a proxy in front of Bedrock or Vertex) can point Tares at it. Two ways, and both reach every model call, for trigger-woken agents and for Ask alike.

In the console, under Settings → Model access: choose through a gateway, enter the gateway’s URL and the token it issued, save. The change applies from the next model call, no restart. This is the path on a hosted cell, where the environment is not yours to set; the platform key stops being used, exactly as when you store your own key. Stop using the gateway returns to the environment’s gateway if the deployment set one, else to Anthropic.

In the environment, with the same variables Claude Code uses:

ANTHROPIC_BASE_URL=https://llm-gateway.internal # serves the Anthropic Messages format ANTHROPIC_AUTH_TOKEN=<token the gateway issued> # sent as Authorization: Bearer tares up

Values stored in the console win over the environment. The credential is sent one of two ways: a gateway token (stored, or ANTHROPIC_AUTH_TOKEN) as a bearer header, or a key (stored, or ANTHROPIC_API_KEY) as the Anthropic key header, which gateways accept too. A stored gateway token comes first, then a stored key, then the environment’s token, then its key.

Two things to know when a gateway is in the path:

  • The gateway decides what answers. Tares sends a Claude model id and accepts any well-formed reply; a gateway that maps that id to another model is the operator’s choice. The agent prompts and the builder are written and tested against Claude.
  • Spend is priced at Claude rates. The console’s spend meter prices runs from Tares’s own price table, by the model name in the reply. Behind a gateway, the gateway’s bill is the truth; a reply naming a model Tares does not price shows as an uncosted run.

The environment variables are read when taresd starts; the console values are read on every call.

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. If the daemon finds leftovers from a pre-1.0 install, an old environment variable or an old database file, it stops at startup and tells you exactly what to rename.

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 container

The 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.

Last updated on