Skip to content

Commit a8d3f6e

Browse files
gatezhclaude
andauthored
perf(docker): optimize builds with cache mounts and parallel stages (#47)
* perf(docker): optimize builds with cache mounts and parallel stages - Use BuildKit cache mounts for apt, npm, and apt/lists across all apt-get and npm install steps (eliminates manual cleanup) - Extract rtk and ralphex downloads into parallel multi-stage builds with COPY --from for faster image builds - Add SHELL pipefail directive for safer curl|sh piping - Consolidate OCI LABEL instructions into single layer - Add OCI labels to both default and sandbox targets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: verify rtk and ralphex versions in post-build checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 58aa1e7 commit a8d3f6e

3 files changed

Lines changed: 78 additions & 42 deletions

File tree

‎.github/workflows/build-claude-code.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,19 @@ jobs:
7575
matrix:
7676
include:
7777
- image-suffix: claude-code
78-
verify-command: "bun --version || true && claude --version && mise --version && fish --version"
78+
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version"
7979
runner: ubuntu-24.04
8080
arch: amd64
8181
- image-suffix: claude-code
82-
verify-command: "bun --version || true && claude --version && mise --version && fish --version"
82+
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version"
8383
runner: ubuntu-24.04-arm
8484
arch: arm64
8585
- image-suffix: claude-code-sandbox
86-
verify-command: "claude --version && mise --version && fish --version && which iptables"
86+
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version"
8787
runner: ubuntu-24.04
8888
arch: amd64
8989
- image-suffix: claude-code-sandbox
90-
verify-command: "claude --version && mise --version && fish --version && which iptables"
90+
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version"
9191
runner: ubuntu-24.04-arm
9292
arch: arm64
9393
runs-on: ${{ matrix.runner }}

‎claude-code/.devcontainer/Dockerfile‎

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,36 @@
1111
# docker build --target sandbox -t claude-code:sandbox .
1212
# ═══════════════════════════════════════════════════════════════════════════════
1313

14+
# ═════════════════════════════════════════════════════════════════════════════
15+
# Parallel download stages — BuildKit runs these concurrently
16+
# ═════════════════════════════════════════════════════════════════════════════
17+
18+
# ── rtk (token-optimized CLI proxy) ──────────────────────────────────────
19+
FROM alpine:3.21 AS rtk-download
20+
RUN apk add --no-cache curl jq
21+
RUN set -eux; \
22+
ARCH="$(uname -m)"; \
23+
case "$ARCH" in \
24+
x86_64) RTK_TARGET="x86_64-unknown-linux-musl" ;; \
25+
aarch64) RTK_TARGET="aarch64-unknown-linux-gnu" ;; \
26+
esac; \
27+
RTK_VERSION=$(curl -fsSL https://api.github.com/repos/rtk-ai/rtk/releases/latest \
28+
| jq -r '.tag_name' | sed 's/^v//'); \
29+
curl -fsSL -o /tmp/rtk.tar.gz \
30+
"https://github.com/rtk-ai/rtk/releases/download/v${RTK_VERSION}/rtk-${RTK_TARGET}.tar.gz"; \
31+
tar -xzf /tmp/rtk.tar.gz -C /usr/local/bin rtk
32+
33+
# ── ralphex (autonomous plan execution) ──────────────────────────────────
34+
FROM alpine:3.21 AS ralphex-download
35+
RUN apk add --no-cache curl jq
36+
RUN set -eux; \
37+
ARCH="$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"; \
38+
RALPHEX_VERSION=$(curl -fsSL https://api.github.com/repos/umputun/ralphex/releases/latest \
39+
| jq -r '.tag_name' | sed 's/^v//'); \
40+
curl -fsSL -o /tmp/ralphex.tar.gz \
41+
"https://github.com/umputun/ralphex/releases/download/v${RALPHEX_VERSION}/ralphex_${RALPHEX_VERSION}_linux_${ARCH}.tar.gz"; \
42+
tar -xzf /tmp/ralphex.tar.gz -C /usr/local/bin ralphex
43+
1444
# ─── BASE ─────────────────────────────────────────────────────────────────────
1545
FROM node:24-trixie-slim AS base
1646

@@ -27,7 +57,9 @@ ARG PLAYWRIGHT_VERSION=1.58.2
2757
# - jq: JSON processing (firewall script, onboarding patch)
2858
# - less: pager for git delta output
2959
# - sudo: privilege escalation for firewall setup
30-
RUN apt-get update && apt-get install -y --no-install-recommends \
60+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
61+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
62+
apt-get update && apt-get install -y --no-install-recommends \
3163
ca-certificates \
3264
curl \
3365
fish \
@@ -36,8 +68,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
3668
git \
3769
jq \
3870
less \
39-
sudo \
40-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
71+
sudo
4172

4273
# npm global directory with proper permissions for node user
4374
# Pre-create /lib to prevent "ENOENT" errors during npx commands
@@ -81,9 +112,11 @@ ENV VISUAL="code --wait"
81112

82113
# ── Starship + Mise (install as root, configure as node) ───────────────────────
83114
USER root
115+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
84116
RUN curl -sS https://starship.rs/install.sh | sh -s -- --yes
85117
RUN curl https://mise.run | sh \
86118
&& cp /root/.local/bin/mise /usr/local/bin/mise
119+
SHELL ["/bin/sh", "-c"]
87120

88121
# Configure starship and fish shell.
89122
# Mise is installed as a tool manager — projects run `mise install` at container
@@ -97,27 +130,12 @@ RUN mkdir -p /home/node/.config/fish \
97130
ENV PATH="/home/node/.local/share/mise/shims:$PATH"
98131
ENV MISE_TRUSTED_CONFIG_PATHS="/workspace"
99132

100-
# ── Dev tools (always latest) ─────────────────────────────────────────────────
133+
# ── Dev tools (copied from parallel download stages) ─────────────────────────
101134
# rtk (token-optimized CLI proxy) and ralphex (autonomous plan execution).
102135
# Like Claude Code itself, these are dev infrastructure — not project dependencies.
103136
# Downloaded from GitHub Releases; refreshed on each daily image rebuild.
104-
USER root
105-
RUN set -eux; \
106-
ARCH="$(uname -m)"; \
107-
# ── rtk ──
108-
case "$ARCH" in \
109-
x86_64) RTK_TARGET="x86_64-unknown-linux-musl" ;; \
110-
aarch64) RTK_TARGET="aarch64-unknown-linux-gnu" ;; \
111-
esac; \
112-
RTK_VERSION=$(curl -fsSL https://api.github.com/repos/rtk-ai/rtk/releases/latest | jq -r '.tag_name' | sed 's/^v//'); \
113-
curl -fsSL "https://github.com/rtk-ai/rtk/releases/download/v${RTK_VERSION}/rtk-${RTK_TARGET}.tar.gz" \
114-
| tar -xz -C /usr/local/bin rtk; \
115-
# ── ralphex ──
116-
RALPHEX_ARCH=$(echo "$ARCH" | sed 's/x86_64/amd64/;s/aarch64/arm64/'); \
117-
RALPHEX_VERSION=$(curl -fsSL https://api.github.com/repos/umputun/ralphex/releases/latest | jq -r '.tag_name' | sed 's/^v//'); \
118-
curl -fsSL "https://github.com/umputun/ralphex/releases/download/v${RALPHEX_VERSION}/ralphex_${RALPHEX_VERSION}_linux_${RALPHEX_ARCH}.tar.gz" \
119-
| tar -xz -C /usr/local/bin ralphex
120-
USER node
137+
COPY --from=rtk-download /usr/local/bin/rtk /usr/local/bin/rtk
138+
COPY --from=ralphex-download /usr/local/bin/ralphex /usr/local/bin/ralphex
121139

122140
# ── Playwright (headless shell for browser testing) ───────────────────────────
123141
# Two-layer strategy for multi-project compatibility:
@@ -128,7 +146,9 @@ USER node
128146
# at container creation to ensure the binary matches their @playwright/test.
129147
# That command is idempotent: no-op when versions match, ~10s download if not.
130148
USER root
131-
RUN npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium
149+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
150+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
151+
npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium
132152
USER node
133153
RUN npx -y playwright@${PLAYWRIGHT_VERSION} install --only-shell
134154
ENV PLAYWRIGHT_VERSION=${PLAYWRIGHT_VERSION}
@@ -142,7 +162,8 @@ ENV PLAYWRIGHT_VERSION=${PLAYWRIGHT_VERSION}
142162
# See: https://code.claude.com/docs/en/getting-started#install-with-npm
143163
# Auto-updates don't matter here — the image rebuilds daily.
144164
USER root
145-
RUN npm install -g @anthropic-ai/claude-code
165+
RUN --mount=type=cache,target=/root/.npm \
166+
npm install -g @anthropic-ai/claude-code
146167
USER node
147168

148169
# ─── DEFAULT — full dev environment ───────────────────────────────────────────
@@ -161,29 +182,38 @@ USER node
161182
# arm64: system Chromium (Chrome for Testing has no ARM64 Linux builds)
162183
ARG AGENT_BROWSER_VERSION=latest
163184
USER root
164-
RUN npm install -g agent-browser@${AGENT_BROWSER_VERSION} \
185+
RUN --mount=type=cache,target=/root/.npm \
186+
--mount=type=cache,target=/var/cache/apt,sharing=locked \
187+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
188+
npm install -g agent-browser@${AGENT_BROWSER_VERSION} \
165189
&& ARCH=$(dpkg --print-architecture) \
166190
&& if [ "$ARCH" = "amd64" ]; then \
167191
agent-browser install --with-deps; \
168192
else \
169-
apt-get update && apt-get install -y --no-install-recommends chromium \
170-
&& apt-get clean && rm -rf /var/lib/apt/lists/*; \
193+
apt-get update && apt-get install -y --no-install-recommends chromium; \
171194
fi \
172195
&& chown -R node:node /usr/local/share/npm-global
173196
USER node
174197

198+
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" \
199+
org.opencontainers.image.description="Claude Code devcontainer — full dev environment with agent-browser, Playwright, and passwordless sudo" \
200+
org.opencontainers.image.licenses="MIT" \
201+
org.opencontainers.image.title="claude-code" \
202+
org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"
203+
175204
# ─── SANDBOX — network-restricted environment ─────────────────────────────────
176205
FROM base AS sandbox
177206

178207
# Firewall packages (not needed in default target)
179208
USER root
180-
RUN apt-get update && apt-get install -y --no-install-recommends \
209+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
210+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
211+
apt-get update && apt-get install -y --no-install-recommends \
181212
iptables \
182213
ipset \
183214
iproute2 \
184215
dnsutils \
185-
aggregate \
186-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
216+
aggregate
187217

188218
# Firewall sudo rule for node user.
189219
# The init-firewall.sh script is NOT baked into the image — each project
@@ -193,3 +223,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
193223
RUN echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall \
194224
&& chmod 0440 /etc/sudoers.d/node-firewall
195225
USER node
226+
227+
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" \
228+
org.opencontainers.image.description="Claude Code devcontainer — network-restricted sandbox with firewall packages" \
229+
org.opencontainers.image.licenses="MIT" \
230+
org.opencontainers.image.title="claude-code-sandbox" \
231+
org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"

‎devcontainer-claude-bun/.devcontainer/Dockerfile‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ ARG GIT_DELTA_VERSION=0.18.2
1010
ARG ZSH_IN_DOCKER_VERSION=1.2.0
1111

1212
# Install basic development tools and iptables/ipset
13-
RUN apt-get update && apt-get install -y --no-install-recommends \
13+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
14+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
15+
apt-get update && apt-get install -y --no-install-recommends \
1416
ca-certificates \
1517
curl \
1618
less \
@@ -24,8 +26,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
2426
iproute2 \
2527
dnsutils \
2628
aggregate \
27-
jq \
28-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
29+
jq
2930

3031
# Ensure default bun user has access to /usr/local/share
3132
RUN mkdir -p /usr/local/share/bun-global && \
@@ -88,9 +89,8 @@ RUN chmod +x /usr/local/bin/init-firewall.sh && \
8889
chmod 0440 /etc/sudoers.d/bun-firewall
8990
USER bun
9091

91-
# OCI labels for container metadata and GitHub Package integration
92-
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images"
93-
LABEL org.opencontainers.image.description="Claude Code development container - Bun environment with Claude Code CLI, firewall sandbox, and zsh"
94-
LABEL org.opencontainers.image.licenses="MIT"
95-
LABEL org.opencontainers.image.title="devcontainer-claude-bun"
96-
LABEL org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"
92+
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" \
93+
org.opencontainers.image.description="Claude Code development container — Bun environment with Claude Code CLI, firewall sandbox, and zsh" \
94+
org.opencontainers.image.licenses="MIT" \
95+
org.opencontainers.image.title="devcontainer-claude-bun" \
96+
org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"

0 commit comments

Comments
 (0)