--- title: Docker description: Running ClawMetry in a container — mounting runtime directories read-only, persisting the store, the daemon-plus-dashboard split, and compose examples. keywords: clawmetry docker, containerized agent observability, docker compose agent monitoring eyebrow: 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. 2. **Persist `/root/.clawmetry` on a named volume.** Without it the store is discarded on every recreate, and you re-ingest from scratch each time. 3. **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 ```yaml title="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](/docs/config/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](/docs/runtimes/opentelemetry/) or the [ingest API](/docs/runtimes/custom-ingest/) 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 ```