Self-hosted telemetry stack for tracking AI coding-tool usage and cost (Claude Code, Codex, or any OTLP client), built on:
- Grafana Alloy — OTLP ingest (gRPC + HTTP) with bearer-token auth
- VictoriaMetrics — long-term metrics storage (100y retention)
- Loki — log storage (60d retention)
- Grafana — provisioned dashboards, including an AI Usage & Cost dashboard that joins raw token counters against a price database to compute cost at query time
Ingress is handled by an external Traefik reverse proxy; only Traefik ports are exposed to the internet.
- Docker with the Compose plugin
- A running Traefik instance with:
- an entrypoint named
websecure(TLS) - a shared Docker network the stack can join (default name:
traefik_net)
- an entrypoint named
- DNS records for the two hostnames you choose (telemetry ingest + Grafana)
cp .env.example .env
# Edit .env: set your hostnames, a Grafana admin password, and an ingest token
# (generate one with: openssl rand -hex 32)
docker compose up -dGrafana is then available at https://$GRAFANA_HOST, and OTLP clients can push
to https://$TELEMETRY_HOST (gRPC 4317 and HTTP 4318 are routed automatically
by content type / path).
All deployment-specific values live in .env (see .env.example):
| Variable | Purpose |
|---|---|
ALLOY_OTLP_TOKEN |
Bearer token OTLP clients must send to ingest data |
GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD |
Grafana admin login |
TELEMETRY_HOST |
Public hostname for OTLP ingest |
GRAFANA_HOST |
Public hostname for Grafana |
TRAEFIK_NETWORK |
Name of your existing external Traefik network |
Any OTLP exporter works — it just needs to send
Authorization: Bearer $ALLOY_OTLP_TOKEN. For Claude Code:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://telemetry.example.com
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <your ALLOY_OTLP_TOKEN>"Codex CLI can export OTLP telemetry via its config.toml — see the Codex
documentation for the current syntax.
Token prices are maintained in pricing/prices.yaml and
imported into VictoriaMetrics with pricing/import_prices.py. Cost is computed
at query time in Grafana; nothing derived is stored. See
pricing/README.md for how to update prices, and
config/grafana/dashboards/README.md for
the dashboard's data model.
Run the test suite with:
uv run pytest pricing/tests/ -q