Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/build-claude-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,19 @@ jobs:
matrix:
include:
- image-suffix: claude-code
verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
runner: ubuntu-24.04
arch: amd64
- image-suffix: claude-code
verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
runner: ubuntu-24.04-arm
arch: arm64
- image-suffix: claude-code-sandbox
verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
runner: ubuntu-24.04
arch: amd64
- image-suffix: claude-code-sandbox
verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node"
runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ jobs:
add_image "claude-code" \
"claude-code/.devcontainer" \
"claude-code/.devcontainer/Dockerfile" \
"bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \
"bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \
"default"
add_image "claude-code-sandbox" \
"claude-code/.devcontainer" \
"claude-code/.devcontainer/Dockerfile" \
"claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \
"claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \
"sandbox"
fi

Expand Down
9 changes: 9 additions & 0 deletions claude-code/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ ARG GH_VERSION=2.102.0
# - openssh-client: ssh-keygen for SSH-format commit signing (and FIDO2 keys via
# libfido2). git only Recommends it, so --no-install-recommends drops it. Must be
# in base — the sandbox firewall blocks deb.debian.org at runtime. See #110.
# - python3, python3-venv: runtime for the security-guidance and claude-security
# plugin hooks/scripts (stdlib only; ~+41 MB together). python3-minimal is too
# small (no http/urllib); python3-venv (ensurepip, +5 MB) rather than
# python3-pip (+15 MB) lets security-guidance build its claude-agent-sdk venv
# in ~/.claude/security. Keep the byte-compiled stdlib: without it every hook
# call recompiles (~50 ms -> ~240 ms). In base for the same reason as
# openssh-client: the sandbox can't apt-install.
# - sudo: privilege escalation for firewall setup
# - zsh: interactive shell; oh-my-zsh + powerlevel10k configured below
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
Expand All @@ -91,6 +98,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
jq \
less \
openssh-client \
python3 \
python3-venv \
sudo \
zsh

Expand Down
4 changes: 4 additions & 0 deletions claude-code/.devcontainer/init-plugins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ PLUGINS=(
"claude-md-management@claude-plugins-official"
"claude-code-setup@claude-plugins-official"
"posthog@claude-plugins-official"
# Both need python3 (baked into the image). security-guidance reviews every
# edit, turn and commit; claude-security runs on-demand /claude-security scans.
"security-guidance@claude-plugins-official"
"claude-security@claude-plugins-official"
# cloudflare/skills is Cloudflare's own marketplace: current skills + MCP server.
# (The claude-plugins-official copy is a stale snapshot.) wrangler <4.126 also
# auto-installed a 3rd copy into ~/.claude/skills after any command; run
Expand Down
6 changes: 5 additions & 1 deletion claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Projects consume these pre-built images and control their own tool versions via
|-------|------|-----|
| OS | `node:24-trixie-slim` + system packages | Node is needed during the build (Playwright, npm globals) |
| Shell | zsh, oh-my-zsh (`git`, `fzf` plugins), powerlevel10k | Completions, git aliases and prompt integration |
| Tools | gh CLI, git, curl, jq, less, fzf, procps, openssh-client | Standard dev utilities (`openssh-client` provides `ssh`/`ssh-keygen` — enables SSH-format commit signing) |
| Tools | gh CLI, git, curl, jq, less, fzf, procps, openssh-client, python3 (+ venv) | Standard dev utilities (`openssh-client` provides `ssh`/`ssh-keygen` — enables SSH-format commit signing; `python3` runs the `security-guidance` and `claude-security` plugins) |
| Mise | The tool manager itself (not the tools) | Projects run `mise install` at container creation for their tool versions |
| gh-stack | `gh` extension, pinned `ARG` bumped by Renovate | Native stacked PRs (`gh stack`). Baked in because `~/.local/share/gh` is not a volume, so a runtime `gh extension install` is lost on rebuild |
| rtk, ralphex | Pinned `ARG`s, bumped by Renovate on each GitHub release | Dev infrastructure (like Claude Code) — the image tracks the versions so projects don't have to |
Expand Down Expand Up @@ -117,6 +117,8 @@ Mark as executable: `chmod +x init-plugins.sh`
| `anthropics/claude-plugins-official` | `claude-md-management` | Audits and updates CLAUDE.md |
| `anthropics/claude-plugins-official` | `claude-code-setup` | Settings, permissions, automation helpers |
| `anthropics/claude-plugins-official` | `posthog` | PostHog product-analytics & LLM-traces skills |
| `anthropics/claude-plugins-official` | `security-guidance` | Security warnings on edits, LLM diff review on Stop, agentic review on commit/push (config via env vars — see [README](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/security-guidance)) |
| `anthropics/claude-plugins-official` | `claude-security` | On-demand `/claude-security` vulnerability scans and verified patch suggestions |
| `cloudflare/skills` | `cloudflare` | Cloudflare skills and MCP server |
| `umputun/ralphex` | `ralphex` | Autonomous plan execution |
| `GoogleChrome/modern-web-guidance` | `modern-web-guidance` | Accessible, performant, secure modern web patterns ([docs](https://developer.chrome.com/docs/modern-web-guidance)) |
Expand All @@ -125,6 +127,8 @@ Mark as executable: `chmod +x init-plugins.sh`

> **Why not the official `typescript-lsp`:** it runs `typescript-language-server`, which wraps `tsserver`. TypeScript 7 (the native Go port) ships no `tsserver.js`, so on a TS 7 project the official plugin fails every request. `typescript-native-lsp` covers TS 7 and older versions in one plugin. The two must not be enabled together: when two plugins claim `.ts`, Claude Code starts only the first one it registers. That's why `init-plugins.sh` also disables `typescript-lsp` when a persisted `~/.claude` volume still has it (`DISABLED_PLUGINS`). TS 6 and older projects need `typescript-language-server` in the project (`bun add -d typescript-language-server`) or installed globally. The official plugin needed that too. Switch back once [anthropics/claude-plugins-official#4492](https://github.com/anthropics/claude-plugins-official/issues/4492) ships native TS 7 support (#194).

**`security-guidance` runtime download:** on first session start the plugin builds a `claude-agent-sdk` venv in `~/.claude/security/` for its agentic commit reviewer — a ~100 MB wheel from PyPI (it bundles its own Claude Code binary), stored once in the `~/.claude` volume, not the image. In the sandbox, add `pypi.org` and `files.pythonhosted.org` to your `init-firewall.sh` allowlist, or the commit reviewer falls back to the single-call diff review (edit warnings and Stop reviews still work). `SECURITY_GUIDANCE_DISABLE=1` in `containerEnv` turns the reviews off, but the venv bootstrap still runs; to skip the download, drop the plugin from your `init-plugins.sh`.

To remove a plugin in your project, delete its entry from the local `init-plugins.sh` — the script is a template, not image-baked, so each consumer controls its own list.

> **Why `init-plugins.sh` stays per-project but `patch-playwright-mcp` doesn't:** `init-plugins.sh` carries project-specific configuration (marketplace list, plugin list) — it's *meant* to be edited per project. The patch script has zero project-specific config and is identical across every consumer, so it's baked into the image and flows through the same Renovate-triggered rebuild + `initializeCommand` image-pull channel as the rest of the image. That boundary is the rule: project-specific config stays per-project; universal logic moves into the image.
Expand Down
3 changes: 2 additions & 1 deletion docs/plans/2026-10-06-claude-code-image-size-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ drive a browser. A core image without them is roughly:
| Core content | Size |
|---|---|
| node:24-trixie-slim | 244 MB |
| apt base (git, zsh, ssh, …) | 147 MB |
| apt base (git, zsh, ssh, python3, …) | 188 MB (python3 + venv: +41 MB, #199) |
| mise | 153 MB |
| Claude Code | 239 MB |
| gh + gh-stack, oh-my-zsh, rtk, ralphex | ~115 MB |
Expand Down Expand Up @@ -192,3 +192,4 @@ on the sandbox network model before adopting.
|---|---|---|---|---|---|
| 2026-10-06 | baseline (`latest`) | 2.82 GB | 812 MB (852 local) | 715 MB | 214 MB |
| 2026-10-06 | Tier 1 (local build, before publish) | 2.32 GB | 633 MB local | 631 MB local, 18 MB on top of default | 108 MB |
| 2026-10-06 | + python3/python3-venv in base for the security plugins (#199, local build) | 2.37 GB | ~+13 MB (gzip of the added files) | shared layer — no extra on top of default | unchanged (still 108 MB; 2 of 23 layers per bump, as before) |
Loading