Skip to content

perf(claude-code): install Claude Code last and cache CI layers - #183

Merged
gatezh merged 2 commits into
masterfrom
perf/claude-code-layer-cache
Oct 1, 2026
Merged

gatezh merged 2 commits into
masterfrom
perf/claude-code-layer-cache

Conversation

@gatezh

@gatezh gatezh commented Oct 1, 2026

Copy link
Copy Markdown
Owner

What

Make a Claude Code version bump republish only the Claude Code layer of claude-code / claude-code-sandbox, instead of most of the image.

Why

Renovate bumps @anthropic-ai/claude-code several times a week (five merges in the week before this PR), and each bump currently republishes almost the whole image:

  • Two published builds 16 hours apart shared only 7 of 22 layers, leaving ~2.26 GB unique. Consumers download ~690 MB compressed per bump, and every orphaned version that a long-lived container still references keeps ~2.26 GB on disk.
  • Two causes, and both need fixing:
    1. build-claude-code.yml never enabled the build cache (docker/github-builder defaults to cache: false), so every CI run rebuilds every layer from scratch with new digests.
    2. ARG CLAUDE_CODE_VERSION was declared in base. An ARG becomes part of the cache key of every later RUN, including in stages built FROM base. The published history confirms it: the Chromium step was recorded as RUN |2 GH_VERSION=… CLAUDE_CODE_VERSION=… apt-get install chromium …. So even with caching on, every bump would have rebuilt Chromium (267 MB compressed) and agent-browser (101 MB).

Changes

  • Dockerfile: ARG CLAUDE_CODE_VERSION=… now sits at the top of the file. Renovate still has exactly one line to bump; I checked its regex against the new file.
  • Dockerfile: moved the Claude Code npm install out of base. It is now the last build step in both default and sandbox, each preceded by a bare ARG CLAUDE_CODE_VERSION.
  • Dockerfile: fixed agent-browser's comment, which now says "see the Claude Code install below" instead of "above".
  • Workflow: both build jobs get cache: true with explicit cache-scopes (claude-code, claude-code-sandbox). The default scope would be the bare target name default. cache-mode stays at its default, min, because every heavy layer is in the final stage.

Testing

Built each target twice locally (arm64), changing only CLAUDE_CODE_VERSION (2.1.284 → 2.1.285), and compared RootFS.Layers:

Target Identical layers Re-run steps
default 22 / 23 only npm install -g @anthropic-ai/claude-code
sandbox 21 / 22 only npm install -g @anthropic-ai/claude-code
  • The CI verify commands for both images pass against the built images.
  • hadolint v2.12.0 (the version hadolint-action@v3.0.0 pins) and actionlint pass.

Notes

  • Chromium freshness trade-off (accepted): the apt-get install chromium layer now refreshes only when something below it changes, such as a new node:24-trixie-slim digest or a bump to gh / gh-stack / rtk / ralphex / mise. Before this change it reinstalled on every build. Expect Debian Chromium security updates to lag by up to about a week. This fits the image's "rebuild on releases, not on a schedule" policy.
  • GHA cache footprint: about 2.9 GB compressed for 2 targets × 2 arches, under the 10 GB repo cap.
  • Not covered: a bump to a tool in base (gh, gh-stack, rtk, ralphex) still invalidates everything above it, including Chromium. That's less frequent and out of scope here, as is ralphex-fe.
  • Post-merge check: caching between CI runs can't be proven locally. After the next two Renovate Claude Code bumps, compare their GHCR layer digests; only the last layer should differ.

gatezh added 2 commits October 1, 2026 12:43
- Move the Claude Code npm install to the end of the default and
  sandbox targets; keep the version as one global ARG for Renovate
- Re-declare the ARG only directly above the install: an ARG joins
  the cache key of every later RUN, which rebuilt Chromium per bump
- Enable the GHA layer cache for both build jobs, with explicit scopes
- Verified locally: a version-only bump changes 1 of 23 layers
  (default) and 1 of 22 (sandbox)
…yer-cache

# Conflicts:
#	claude-code/.devcontainer/Dockerfile
@gatezh
gatezh merged commit bc6e3f4 into master Oct 1, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant