CClawMetryDocs

Configuration

Docker#

The container has no access to your agents unless you mount them. That is the main thing to get right.

Build and run#

bash
docker build -t clawmetry .

docker run -d --name clawmetry \
  -p 8900:8900 \
  -v clawmetry-state:/root/.clawmetry \
  -v ~/.openclaw:/root/.openclaw:ro \
  -v ~/.claude:/root/.claude:ro \
  -v ~/.codex:/root/.codex:ro \
  clawmetry

Three rules:

  1. Mount runtime directories :ro. ClawMetry never writes to them, and the

mount flag makes that structural rather than a promise.

  1. Persist /root/.clawmetry on a named volume. Without it the store is

discarded on every recreate, and you re-ingest from scratch each time.

  1. Mount every runtime you want observed. Detection is a filesystem check;

an unmounted runtime does not exist as far as the container is concerned.

Compose#

docker-compose.yml
services:
  clawmetry:
    build: .
    ports:
      - "127.0.0.1:8900:8900"
    volumes:
      - clawmetry-state:/root/.clawmetry
      - ${HOME}/.openclaw:/root/.openclaw:ro
      - ${HOME}/.claude:/root/.claude:ro
      - ${HOME}/.codex:/root/.codex:ro
      - ${HOME}/.cursor:/root/.cursor:ro
    environment:
      CLAWMETRY_HOME: /root/.clawmetry
      CLAWMETRY_FAMILY_SESSION_LIMIT: "100"
      CLAWMETRY_NO_BROWSER: "1"
    restart: unless-stopped

volumes:
  clawmetry-state:

Note 127.0.0.1:8900:8900 rather than 8900:8900 — the second form publishes the dashboard on every interface, and the dashboard shows your agents' transcripts. → Ports and networking

Splitting daemon and dashboard#

For anything beyond a single-machine setup, run them as two services sharing the state volume. Only the daemon writes.

yaml
services:
  sync:
    build: .
    command: python -m clawmetry.sync
    volumes:
      - clawmetry-state:/root/.clawmetry
      - ${HOME}/.openclaw:/root/.openclaw:ro
    restart: unless-stopped

  dashboard:
    build: .
    command: clawmetry --host 0.0.0.0 --port 8900 --no-debug
    depends_on: [sync]
    ports:
      - "127.0.0.1:8900:8900"
    volumes:
      - clawmetry-state:/root/.clawmetry
    restart: unless-stopped

volumes:
  clawmetry-state:

--host 0.0.0.0 is correct inside the container — the port publishing is what restricts exposure. The dashboard finds the daemon through the shared local_query.json, which is why the state volume must be shared and not just similar.

Paths inside the container#

Runtimes resolve paths relative to the container's home directory, so mount to the path the runtime expects. If you run as a non-root user, mount to that user's home instead of /root, or set the override:

yaml
environment:
  OPENCLAW_HOME: /data/openclaw
  CLAUDE_CONFIG_DIR: /data/claude

Cloud sync from a container#

yaml
environment:
  CLAWMETRY_API_KEY: ${CLAWMETRY_KEY}
  CLAWMETRY_NODE_ID: prod-runner-01

Set CLAWMETRY_NODE_ID explicitly. A container's hostname is a random hex string that changes on every recreate, which produces a fleet list of one-session ghosts.

Pass the encryption key too, so a recreated container keeps reading its own history:

yaml
environment:
  CLAWMETRY_ENC_KEY: ${CLAWMETRY_ENC_KEY}

Observing agents in other containers#

If your agents run in their own containers, share a volume between the agent and ClawMetry:

yaml
services:
  my-agent:
    image: my-agent
    volumes:
      - agent-state:/root/.openclaw

  clawmetry:
    build: .
    volumes:
      - agent-state:/root/.openclaw:ro
      - clawmetry-state:/root/.clawmetry

For agents you cannot co-locate, use OTLP or the ingest API instead of sharing a filesystem.

Health#

bash
curl -s localhost:8900/healthz
docker exec clawmetry clawmetry status
yaml
healthcheck:
  test: ["CMD", "curl", "-fsS", "http://localhost:8900/healthz"]
  interval: 30s
  timeout: 5s
  retries: 3

Resources#

DuckDB wants memory, and a container that is being OOM-killed mid-write is the worst case for the store. Give it headroom, or cap DuckDB explicitly:

yaml
environment:
  CLAWMETRY_DUCKDB_MEMORY_LIMIT: 1GB
  CLAWMETRY_DUCKDB_THREADS: "2"
deploy:
  resources:
    limits:
      memory: 2G
Cookie preferences