Skip to content

Docker 部署 TokenTelemetry:在本机分析 AI 编码会话

TokenTelemetry 从 Claude Code、Codex、Gemini CLI、Cursor、GitHub Copilot 等工具写入本机的会话文件中提取 Token、模型、成本估算、工具调用、计划和项目活动。Docker 部署适合希望隔离 Python/Node.js 依赖,同时保留本地日志分析能力的个人开发环境。

Codex 部署只把宿主机 ~/.codex/sessions 只读挂载到后端。该范围足以扫描新版 Codex 的 rollout-*.jsonl,并避免直接暴露 ~/.codex/auth.jsonconfig.toml 和其他认证配置。下方 Compose 已完成静态解析;当前环境未启动容器,也没有读取真实会话内容做功能验收。

部署结论

项目本文采用的值
GitHub 地址VasiHemanth/tokentelemetry
官方镜像后端与前端 GHCR Packages
项目版本1.0.0
正式部署Docker Compose,前端与后端配套运行
快速体验不提供 docker run:前后端、构建期 API 端口、日志挂载和数据卷需要保持一致
适用范围单用户、本机历史会话分析;不作为公网多用户观测平台
主机架构Linux amd64arm64
最低资源官方没有给出容器部署的 CPU、内存和磁盘下限
Web 页面宿主机 127.0.0.1:13000 -> 前端 3000
后端 API宿主机 127.0.0.1:18000 -> 后端 8000
日志来源${HOME}/.codex/sessions -> /root/.codex/sessions:ro
应用数据Docker volume tokentelemetry_data -> /tt-data
依赖服务无需外部数据库或缓存
验证范围官方文档与 Compose 静态检查;未运行容器
部署日期2026-08-28

TokenTelemetry 当前没有 Git tag,项目元数据版本为 1.0.0。前端与后端需要使用同一次构建的镜像,避免 API 或页面字段不一致。

数据流与边界

浏览器只访问宿主机回环端口。前端根据构建时写入的 18000 请求后端;后端读取 Codex 会话文件,把扫描缓存、历史汇总和界面设置写入独立数据卷。Compose 不挂载 Docker socket,也不需要 API key。

Mermaid 流程图
查看源码
flowchart LR
  Browser[本机浏览器] -->|127.0.0.1:13000| Frontend[TokenTelemetry frontend]
  Frontend -->|127.0.0.1:18000| Backend[TokenTelemetry backend]
  Sessions[Codex sessions] -->|只读挂载| Backend
  Backend --> Data[(tokentelemetry_data)]

只读挂载能阻止容器修改 Codex 日志,不能阻止后端读取日志内容。会话文件可能包含提示词、模型输出、工具参数、命令、路径和项目名称;第三方镜像一旦被替换或被攻破,读取到的内容仍可能被外传。固定镜像、限制挂载范围和关闭非必要外联缺一不可。

部署前准备

主机需要 Docker Engine 或 Docker Desktop,以及 Docker Compose v2:

bash
docker --version
docker compose version

确认 Codex 已产生会话文件,并检查两个宿主机端口:

bash
find "${HOME}/.codex/sessions" -type f -name 'rollout-*.jsonl' -print -quit
ss -lntp 2>/dev/null | grep -E ':(13000|18000):space:' || true

macOS 没有 ss 时使用:

bash
lsof -nP -iTCP:13000 -sTCP:LISTEN
lsof -nP -iTCP:18000 -sTCP:LISTEN

第一条命令没有输出通常表示 Codex 尚未生成会话,或 CODEX_HOME 使用了其他位置。先确认真实日志目录,不要为了让页面出现数据而挂载整个主目录。

Docker Compose 正式部署

准备部署目录

下面以 /opt/tokentelemetry 为长期目录。Compose 文件不包含认证信息,但目录仍应只允许管理员修改,避免本机其他账号替换镜像或挂载路径:

bash
sudo install -d -m 0750 -o "$(id -u)" -g "$(id -g)" /opt/tokentelemetry
cd /opt/tokentelemetry

保存 .env

dotenv
TZ=Asia/Shanghai
TT_DATA_VOLUME=tokentelemetry_data

.env 只保存时区和卷名,不存放 Codex 或模型凭据。

写入 Compose 配置

保存为 /opt/tokentelemetry/compose.yaml

yaml
services:
  backend:
    image: ghcr.io/vasihemanth/tokentelemetry-backend:sha-cecce1c@sha256:9abeee7b470c0676368b54df824fb62261f54686a2cb905309a499cde32a923c
    environment:
      TT_HOST: 0.0.0.0
      TT_API_PORT: "8000"
      TOKENTELEMETRY_DATA_DIR: /tt-data
      TOKENTELEMETRY_HOME: /tt-data
      TZ: ${TZ:-UTC}
      DO_NOT_TRACK: "1"
      TT_NO_UPDATE_CHECK: "1"
    volumes:
      - tt_data:/tt-data
      - ${HOME}/.codex/sessions:/root/.codex/sessions:ro
    ports:
      - 127.0.0.1:18000:8000
    healthcheck:
      test:
        - CMD-SHELL
        - python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('localhost', 8000)); s.close()"
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped

  frontend:
    image: ghcr.io/vasihemanth/tokentelemetry-frontend:sha-cecce1c@sha256:efd3d24840393d6f09037a0a3e400810630a169da9e7f6c5d9c40a4326e1aff7
    ports:
      - 127.0.0.1:13000:3000
    depends_on:
      backend:
        condition: service_started
    restart: unless-stopped

  maintenance:
    image: ghcr.io/vasihemanth/tokentelemetry-backend:sha-cecce1c@sha256:9abeee7b470c0676368b54df824fb62261f54686a2cb905309a499cde32a923c
    profiles:
      - maintenance
    network_mode: none
    volumes:
      - tt_data:/tt-data
      - ./backups:/backup
    entrypoint:
      - /bin/sh
      - -lc

volumes:
  tt_data:
    name: ${TT_DATA_VOLUME:-tokentelemetry_data}

TT_HOST=0.0.0.0 只控制后端在容器内部监听;宿主机仍由 127.0.0.1:18000:8000 限制在回环地址。预构建前端已经把 API 宿主机端口 18000 编译进客户端,不能只修改后端端口。需要其他端口时,应使用与后端相同版本的源码重新构建前端并设置 NEXT_PUBLIC_API_PORT

SELinux enforcing 主机若拒绝读取 Codex 日志,把挂载末尾改为 :ro,z,让 Docker 为该目录设置共享容器标签。不要把 :ro 删除。

解析并启动

先检查最终配置是否仍然只发布回环端口、只挂载预期目录:

bash
cd /opt/tokentelemetry
docker compose config --quiet
docker compose config | grep -E '127\.0\.0\.1|\.codex/sessions|/tt-data'

静态解析没有输出且退出码为 0 后,再拉取和启动:

bash
docker compose pull
docker compose up -d
docker compose ps

backend 应先进入 running,健康检查通过后显示 healthyfrontend 应保持 running

验证部署结果

容器与镜像

bash
docker compose ps
docker compose images
docker inspect --format '{{.State.Health.Status}}' \
  "$(docker compose ps -q backend)"

预期后端健康状态为 healthydocker compose images 应显示两个 sha-cecce1c 镜像,不能出现 latest

页面与 API

bash
curl -fsS http://127.0.0.1:18000/
curl -fsS http://127.0.0.1:18000/agents
curl -fsS -o /dev/null -w 'sessions HTTP %{http_code}\n' \
  'http://127.0.0.1:18000/sessions?fresh=1'
curl -fsS -o /dev/null -w 'frontend HTTP %{http_code}\n' \
  http://127.0.0.1:13000/

后端根路径应返回 {"message":"TokenTelemetry API is running"}/agents 应包含 codex,两个 HTTP 检查应返回 200。不要把完整 /sessions 响应保存到公开日志;响应可能带有会话标题、项目路径和用量明细。

日志与挂载

bash
docker compose logs --tail=100 backend frontend
docker compose exec backend sh -lc \
  "find /root/.codex/sessions -type f -name 'rollout-*.jsonl' -print -quit"
docker inspect --format '{{range .Mounts}}{{println .Source "->" .Destination "rw=" .RW}}{{end}}' \
  "$(docker compose ps -q backend)"

日志不应持续出现重启、Python 异常或前端请求失败。Codex 挂载的 rw 应为 false/tt-data 对应的命名卷应为 true

持久化

在页面 Settings 中修改一个不敏感的显示偏好,然后重建容器:

bash
docker compose up -d --force-recreate backend frontend
docker compose ps

重新打开 http://127.0.0.1:13000/。显示偏好仍存在,才能证明 tokentelemetry_data 在容器重建后保留;这个检查不等于备份恢复演练。

开始分析 Codex 会话

打开 http://127.0.0.1:13000/ 后,按以下顺序确认数据是否合理:

  1. Dashboard 检查已识别 Agent、近期活动、模型分布和 Token 变化。
  2. Projects 按代码目录查看会话、工具使用和模型对比;Git worktree 可能归并到主仓库项目。
  3. Sessions 打开单次会话时间线,检查提示词、推理片段、工具调用、子 Agent 和最终结果。
  4. Analytics 按 Agent、模型和时间范围比较 Token 与估算成本。
  5. Settings 检查计费模式、保留策略和隐私开关。

成本数据来自会话记录和 TokenTelemetry 的价格表,不能替代模型提供方账单。Budget 只负责观察和提醒,不会阻止 Agent 继续消耗。Codex Skill 使用通常根据会话中读取 skills/<name>/SKILL.md 的工具调用推断,不是 Codex 提供的结构化 Skill 事件;MCP 和工具统计也受日志字段完整度影响。

当前最小挂载不会读取 ~/.codex/config.toml、插件缓存或完整 Skills 目录。因此页面能分析会话中已经记录的工具、MCP 和 Skill 痕迹,但无法完整展示本机已配置却未在会话中出现的项目。只有明确需要配置盘点时才增加单文件或单目录只读挂载,并先审查其中是否包含密钥、内部地址或私有指令。

接入其他 Agent

后端只能分析容器中可见的日志。增加其他 Agent 前,先在宿主机确认实际目录,再把最小必要路径挂载为只读。官方 Compose 给出的常见目标包括:

Agent容器内默认目录挂载建议
Claude Code/root/.claude优先定位会话子目录;整个目录可能包含配置与认证资料
Gemini CLI/root/.gemini只读挂载,并检查目录中的账号与项目配置
Cline/root/.cline需要 CLI SQLite 或扩展存储时分别挂载
Cursor / VS Code Copilot/root/.config/...路径含空格,Compose 中必须加引号
Hermes Agent/root/.hermesTokenTelemetry 需要与 Hermes 日志位于同一主机

每增加一个挂载,后端可读取的数据范围都会扩大。不要把 ${HOME}、SSH 目录、云凭据目录或整个开发工作区挂进容器,也不要使用 privileged/var/run/docker.sock

日常运维

bash
cd /opt/tokentelemetry
docker compose ps
docker compose logs --tail=100 backend frontend
docker compose restart backend frontend
docker compose exec backend sh -lc 'du -sh /tt-data; find /tt-data -maxdepth 2 -type f -print | head -n 30'

风险提示:docker compose down 会删除容器和网络,但保留 tokentelemetry_datadocker compose down -v 和官方 make clean 会继续删除数据卷。执行任何卷删除操作前必须完成并验证“备份与恢复”章节中的归档,不要把这两个命令当成普通停止命令。

备份与恢复

Codex 原始会话仍保存在宿主机 ~/.codex/sessions,不属于 TokenTelemetry 数据卷。tokentelemetry_data 保存历史汇总、扫描缓存、摘要、偏好和计费配置,应按敏感数据处理。

maintenance profile 默认不会启动,也不挂载 Codex 会话目录。先创建独立备份目录,再停止两项服务并归档命名卷:

bash
cd /opt/tokentelemetry
BACKUP_NAME="$(date +%F-%H%M%S)/tt-data.tar.gz"
BACKUP_DIR="${PWD}/backups/${BACKUP_NAME%/*}"
install -d -m 0700 "$BACKUP_DIR"
cp compose.yaml .env "$BACKUP_DIR/"
docker compose stop frontend backend
docker compose --profile maintenance run --rm \
  -e BACKUP_NAME="$BACKUP_NAME" maintenance \
  'tar -C /tt-data -czf "/backup/$BACKUP_NAME" .'
docker compose start backend frontend
sudo chmod 600 "$BACKUP_DIR/tt-data.tar.gz"
tar -tzf "$BACKUP_DIR/tt-data.tar.gz" | tail -n 20

归档检查通过后,把整个时间戳目录复制到加密的异机存储。恢复时不要清空当前卷;先创建新卷并把归档解压进去:

bash
cd /opt/tokentelemetry
BACKUP_DIR="/opt/tokentelemetry/backups/2026-08-28-120000"
test -f "$BACKUP_DIR/tt-data.tar.gz"
tar -tzf "$BACKUP_DIR/tt-data.tar.gz" >/dev/null
RESTORE_VOLUME="tokentelemetry_data_restore_$(date +%Y%m%d%H%M%S)"
docker volume create "$RESTORE_VOLUME"
cp .env .env.before-restore
sed -i.bak "s/^TT_DATA_VOLUME=.*/TT_DATA_VOLUME=${RESTORE_VOLUME}/" .env
BACKUP_NAME="${BACKUP_DIR##*/}/tt-data.tar.gz"
docker compose --profile maintenance run --rm \
  -e BACKUP_NAME="$BACKUP_NAME" maintenance \
  'tar -C /tt-data -xzf "/backup/$BACKUP_NAME"'
docker compose up -d --force-recreate backend frontend
curl -fsS http://127.0.0.1:18000/

示例中的 BACKUP_DIR 必须替换为实际归档目录。页面设置、项目和历史汇总确认无误后,保留旧卷一段时间;恢复失败时把 .env.before-restore 移回 .env 并重新创建容器。

升级与回滚

升级前备份数据卷,并在官方仓库确认目标版本的 Compose、Dockerfile、更新说明和配套镜像。分别检查后端与前端镜像:

bash
docker buildx imagetools inspect \
  ghcr.io/vasihemanth/tokentelemetry-backend:sha-cecce1c
docker buildx imagetools inspect \
  ghcr.io/vasihemanth/tokentelemetry-frontend:sha-cecce1c

确认目标版本后,同时替换 Compose 中的两个 tag 和 digest,再执行:

bash
cd /opt/tokentelemetry
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
curl -fsS http://127.0.0.1:18000/
docker compose logs --tail=100 backend frontend

前后端必须来自同一次构建,避免 API 或页面字段不匹配。官方没有为每个版本提供数据库迁移和降级兼容承诺;回滚不能只替换镜像。出现历史数据异常时,恢复升级前数据卷,并把两个镜像一起切回升级前版本。

安全边界

  • ~/.codex/sessions 可能包含完整对话、工具参数、文件路径、命令和模型输出。只读不等于不可提取,镜像来源与 digest 必须纳入升级审查。
  • 两个端口只绑定宿主机 127.0.0.1。远程访问优先使用双端口 SSH 隧道:ssh -N -L 13000:127.0.0.1:13000 -L 18000:127.0.0.1:18000 user@host
  • DO_NOT_TRACK=1 关闭默认启用的匿名产品使用遥测,TT_NO_UPDATE_CHECK=1 关闭 GitHub 更新检查。页面 Settings 应显示相应开关被环境策略关闭。
  • 不向 Compose 加入 Codex auth.json、模型 API key、Docker socket、privileged 或宿主机根目录。
  • TokenTelemetry 的后端 API 没有为默认本机模式配置登录。任何反向代理、公网发布、LAN 或 tailnet 暴露都需要额外认证、TLS 和来源限制,不能只把 127.0.0.1 改成 0.0.0.0
  • tokentelemetry_data 会形成原始日志之外的历史副本和统计缓存。备份、卷快照和支持工单都应脱敏并限制访问。

常见问题

页面能打开但没有 Codex 会话

先确认宿主机和容器看到相同的 rollout 文件:

bash
find "${HOME}/.codex/sessions" -type f -name 'rollout-*.jsonl' -print -quit
docker compose exec backend sh -lc \
  "find /root/.codex/sessions -type f -name 'rollout-*.jsonl' -print -quit"
curl -fsS http://127.0.0.1:18000/agents

宿主机有文件而容器没有时,检查 Compose 变量展开、Docker Desktop 文件共享和 SELinux 标签。Codex 使用自定义 CODEX_HOME 时,把真实 sessions 目录映射到容器内 /root/.codex/sessions

前端出现空白数据或 API 请求失败

bash
curl -fsS http://127.0.0.1:18000/
docker compose logs --tail=100 frontend backend

预构建前端在构建时写入后端宿主机端口 18000。只修改端口映射会让浏览器继续请求旧端口;恢复 127.0.0.1:18000:8000,或使用与后端相同版本的源码重新构建前端并设置新的 NEXT_PUBLIC_API_PORT

Linux 主机提示挂载权限或 permission denied

bash
docker compose config | grep -A4 '/root/.codex/sessions'
ls -ld "${HOME}/.codex" "${HOME}/.codex/sessions"

普通权限问题应修正宿主机目录的所有者和遍历权限,不要改成所有本机用户可读写。SELinux enforcing 主机把挂载改成 ${HOME}/.codex/sessions:/root/.codex/sessions:ro,z 后重新创建 backend。

端口 13000 或 18000 已被占用

bash
ss -lntp 2>/dev/null | grep -E ':(13000|18000):space:' || true
lsof -nP -iTCP:13000 -sTCP:LISTEN 2>/dev/null || true
lsof -nP -iTCP:18000 -sTCP:LISTEN 2>/dev/null || true

前端端口 13000 可以更换宿主机映射;后端端口 18000 与预构建前端绑定,不能单独修改。需要整体换端口时使用与后端相同版本的源码重建前端。

部署完成后

  1. 对照一条已知 Codex 会话检查模型、Token、工具和项目归属,不把成本估算当成账单。
  2. 设置合适的历史保留策略,并定期检查 tokentelemetry_data 的增长与备份恢复结果。
  3. 增加其他 Agent 前逐个审查日志目录,保持只读和最小挂载,不一次性开放整个主目录。

总结

TokenTelemetry 通过配套的前后端镜像,在宿主机回环端口提供 AI 编码会话分析页面。Codex 部署只需把 ~/.codex/sessions 只读挂载到后端,就能扫描历史 rollout,并把汇总、缓存和界面设置保存在独立数据卷中。

TokenTelemetry 展示的是本地日志能够证明的历史数据。成本属于估算,Skill 使用可能来自路径推断,完整请求链路、压缩前后指标和模型提供方账单仍需要其他数据源;会话挂载和持久化卷都应视为敏感数据。

参考资料

全部公开文章由同一个站点构建和发布。