From d48b1a668dc685652bd494c5256688e99b743a59 Mon Sep 17 00:00:00 2001 From: Rafik Farhad Date: Sat, 20 Jun 2026 13:15:13 -0500 Subject: [PATCH 1/2] feat(docker): read-only claude mount with isolated writable DB volume Store usage.db in a separate /data volume so ~/.claude stays read-only in the container. CLAUDE_USAGE_DB env var lets all three modules resolve the DB path at runtime instead of hardcoding ~/.claude/usage.db. Disclaimer: If this looks smart, thank the AI. If not, blame me. --- Dockerfile | 13 +++++++++++++ cli.py | 2 +- dashboard.py | 2 +- scanner.py | 2 +- scripts/run-docker.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 Dockerfile create mode 100755 scripts/run-docker.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fce40688 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY scanner.py cli.py dashboard.py ./ + +ENV HOST=0.0.0.0 +ENV PORT=8080 +ENV CLAUDE_USAGE_DB=/data/usage.db + +EXPOSE 8080 + +CMD ["python3", "cli.py", "dashboard", "--no-browser"] diff --git a/cli.py b/cli.py index 98f3a124..b2bfbddf 100644 --- a/cli.py +++ b/cli.py @@ -16,7 +16,7 @@ from scanner import VERSION -DB_PATH = Path.home() / ".claude" / "usage.db" +DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db")) PRICING = { # Fable / Mythos — Anthropic's most capable class, priced at 2x Opus. diff --git a/dashboard.py b/dashboard.py index e2ac2f6f..01ad139c 100644 --- a/dashboard.py +++ b/dashboard.py @@ -12,7 +12,7 @@ from scanner import VERSION -DB_PATH = Path.home() / ".claude" / "usage.db" +DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db")) # Which surface is rendering the dashboard: "web" (standalone `cli.py dashboard`) # or "vscode" (embedded in the extension's sidebar webview). serve() sets this diff --git a/scanner.py b/scanner.py index 72747cda..22b0812b 100644 --- a/scanner.py +++ b/scanner.py @@ -19,7 +19,7 @@ PROJECTS_DIR = Path.home() / ".claude" / "projects" XCODE_PROJECTS_DIR = Path.home() / "Library" / "Developer" / "Xcode" / "CodingAssistant" / "ClaudeAgentConfig" / "projects" -DB_PATH = Path.home() / ".claude" / "usage.db" +DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db")) DEFAULT_PROJECTS_DIRS = [PROJECTS_DIR, XCODE_PROJECTS_DIR] # Higher number = higher priority when choosing a session's primary model. diff --git a/scripts/run-docker.sh b/scripts/run-docker.sh new file mode 100755 index 00000000..4dd26389 --- /dev/null +++ b/scripts/run-docker.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +IMAGE="claude-usage" +CONTAINER="claude-usage" +NETWORK="claude-usage-net" +PORT=9898 + +echo "▶ Checking for running container..." +if docker ps -q --filter "name=^${CONTAINER}$" | grep -q .; then + echo "⏹ Stopping ${CONTAINER}..." + docker stop "$CONTAINER" +fi + +echo "🔗 Ensuring isolated network..." +if ! docker network inspect "$NETWORK" &>/dev/null; then + docker network create \ + --opt com.docker.network.bridge.enable_ip_masquerade=false \ + "$NETWORK" +fi + +echo "⬇ Pulling latest..." +cd "$REPO_DIR" +git pull + +echo "🔨 Building image..." +docker build -t "$IMAGE" . + +echo "🚀 Starting container..." +docker run --rm -d \ + --name "$CONTAINER" \ + --network "$NETWORK" \ + -p "$PORT:8080" \ + -v "$HOME/.claude:/root/.claude:ro" \ + -v "${CONTAINER}-data:/data" \ + -e HOST=0.0.0.0 \ + "$IMAGE" + +echo "✅ Running at http://localhost:${PORT}" From 7bf7ae81a80401338a42f4d682493ad07cd36487 Mon Sep 17 00:00:00 2001 From: Rafik Farhad Date: Sat, 20 Jun 2026 13:18:01 -0500 Subject: [PATCH 2/2] docs: add Docker usage section to README Documents the run-docker.sh script, the read-only ~/.claude mount, and the named volume for the SQLite database. Also adds Dockerfile and scripts/run-docker.sh to the Files table. Disclaimer: If this looks smart, thank the AI. If not, blame me. --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index e2ffe9a4..8b45ebca 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,18 @@ cd claude-usage python cli.py dashboard ``` +### Docker +``` +git clone https://github.com/phuryn/claude-usage +cd claude-usage +bash scripts/run-docker.sh +``` + +Opens the dashboard at **http://localhost:9898**. + +The script builds the image, then runs the container with: +- `~/.claude` mounted **read-only** — the container can read your transcripts but cannot modify them +- A named Docker volume (`claude-usage-data`) for the SQLite database — persisted across restarts, isolated from your home directory --- @@ -155,3 +167,5 @@ See [vscode-extension/README.md](vscode-extension/README.md) for settings, comma | `cli.py` | `scan`, `today`, `stats`, `dashboard` commands | | `Formula/claude-usage.rb` | Homebrew formula — install with `brew install --formula ` | | `vscode-extension/` | VS Code extension — embeds the dashboard inside VS Code | +| `Dockerfile` | Container image definition | +| `scripts/run-docker.sh` | Build and run the dashboard in Docker with a read-only `~/.claude` mount |