Skip to content

Create Dockerfile and CI pipeline for Claude Code execution image#317

Merged
geoffjay merged 3 commits intomainfrom
issue-286
Mar 11, 2026
Merged

Create Dockerfile and CI pipeline for Claude Code execution image#317
geoffjay merged 3 commits intomainfrom
issue-286

Conversation

@geoffjay
Copy link
Copy Markdown
Owner

Summary

  • Add docker/claude-code/Dockerfile based on node:22-slim with Claude Code CLI, git, curl, jq, openssh-client, non-root agent user (UID 1000), and /workspace workdir
  • Add GitHub Actions workflow for multi-platform builds (amd64/arm64) pushing to ghcr.io/geoffjay/agentd-claude with latest, SHA, and semver tags
  • Add Makefile with docker-build-claude target and standard Rust build targets
  • Add .dockerignore to keep build context minimal

Test plan

  • Dockerfile syntax valid (no build errors in syntax check)
  • GitHub Actions workflow YAML parses correctly
  • Makefile targets listed and functional (make help)
  • Image name matches DEFAULT_IMAGE constant in crates/wrap/src/docker.rs
  • CI builds image successfully on push (verified after merge)
  • docker run --rm agentd-claude:latest --version returns Claude Code version

Closes #286

🤖 Generated with Claude Code

…n image

Create docker/claude-code/Dockerfile based on node:22-slim with:
- Claude Code CLI installed via npm
- Common dev tools (git, curl, jq, openssh-client)
- Non-root 'agent' user (UID 1000) matching DockerBackend defaults
- /workspace working directory with safe.directory configured
- claude entrypoint, no secrets baked in

Add GitHub Actions workflow (.github/workflows/docker-claude-code.yml):
- Triggers on Dockerfile changes, releases, and manual dispatch
- Builds multi-platform images (linux/amd64, linux/arm64)
- Pushes to ghcr.io/geoffjay/agentd-claude with latest, SHA, and
  semver tags
- Uses buildx layer caching for fast rebuilds

Add Makefile with docker-build-claude target and standard Rust targets.
Add .dockerignore to keep build context minimal.

Closes #286

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@geoffjay geoffjay added the review-agent Used to invoke a review by an agent tracking this label label Mar 11, 2026
The node:22-slim base image already has a `node` user at UID 1000.
Use groupmod/usermod to rename it to `agent` instead of groupadd/useradd
which fails with exit code 4 (UID already exists).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@geoffjay
Copy link
Copy Markdown
Owner Author

Code Review: PR #317 — Dockerfile and CI pipeline for Claude Code execution image

The overall structure is solid: minimal base image, non-root user, GHA cache, correct registry auth gating, and multi-platform support. One blocking issue and a handful of minor suggestions.


🔴 Blocking: ENTRYPOINT in Dockerfile conflicts with DockerBackend's CMD

The Dockerfile ends with:

ENTRYPOINT ["claude"]

But build_agent_cmd in docker.rs returns vec\!["claude"] for the claude-code agent type, which gets passed as the cmd field in create_container. In Docker's model, when an image has an ENTRYPOINT and the API caller sets CMD, CMD provides arguments to the ENTRYPOINT — the container runs claude claude.

claude called with claude as its first argument is almost certainly invalid. The container process will likely exit immediately, which means docker exec (used by send_command to inject the real --sdk-url invocation) will fail with "container is not running".

Fix: remove the ENTRYPOINT directive. With no ENTRYPOINT, Docker treats CMD as the full command, so cmd: vec\!["claude"] runs claude (no args) which enters interactive/blocking mode and keeps the container alive for docker exec. The docker run --version smoke test in the README would become docker run --rm agentd-claude:latest --version and still work because CMD can be overridden on the command line.

Alternatively, if the intent is to always keep containers alive for exec regardless of the CMD, consider a supervisor process (e.g. ENTRYPOINT ["/bin/sh", "-c", "exec """, "--"]) — but removing ENTRYPOINT is the simplest fix here.


🟡 Non-blocking: AS base alias is unused

FROM node:22-slim AS base

There is only a single build stage, so the AS base alias has no effect. Either remove it or add a comment noting it is reserved for a future multi-stage split.


🟡 Non-blocking: @anthropic-ai/claude-code is not version-pinned

RUN npm install -g @anthropic-ai/claude-code && \

Every build pulls whatever npm resolves as "latest". This is fine for the latest image tag but means two sha-abc1234 images built on different days can contain different CLI versions, which undermines the SHA tag's reproducibility guarantee. Consider pinning with @anthropic-ai/claude-code@<version> and updating it deliberately (or via a Renovate/Dependabot config), at least as a follow-up.


🟡 Non-blocking: mkdir -p /home/agent after usermod --move-home is redundant

RUN groupmod --new-name agent node && \
    usermod --login agent --home /home/agent --move-home --comment "agentd agent" node && \
    mkdir -p /home/agent && chown agent:agent /home/agent

usermod --move-home already moves /home/node to /home/agent and preserves ownership. The subsequent mkdir -p is a no-op (directory exists) and the chown resets ownership to the same value. Harmless, but the extra step could be dropped.


🟡 Non-blocking: fmt-fix missing from .PHONY

.PHONY: help build test clippy fmt docker-build-claude docker-run-claude

fmt-fix and docker-build-claude-multiarch are both defined as targets but are absent from .PHONY. If a file named fmt-fix ever appears in the repo root, make fmt-fix would silently do nothing.


🟡 Non-blocking: CI verify step only covers amd64

The "Verify image" step runs on ubuntu-latest (always amd64) and pulls the amd64 variant. The arm64 layer is built and pushed but never smoke-tested. Optional: add a second verify step that uses --platform linux/arm64 with QEMU, or simply note in the workflow comment that arm64 is untested beyond a successful buildx push.


# Severity Issue
1 🔴 Blocking ENTRYPOINT + backend CMD combine to run claude claude, container exits immediately
2 🟡 Non-blocking AS base alias unused (single-stage build)
3 🟡 Non-blocking @anthropic-ai/claude-code not version-pinned
4 🟡 Non-blocking mkdir -p /home/agent after --move-home is redundant
5 🟡 Non-blocking fmt-fix and docker-build-claude-multiarch missing from .PHONY
6 🟡 Non-blocking CI verify step only confirms amd64

Copy link
Copy Markdown
Owner Author

@geoffjay geoffjay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline comments on specific lines — see the overall review summary in the PR comments.

@geoffjay geoffjay removed the review-agent Used to invoke a review by an agent tracking this label label Mar 11, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.67%. Comparing base (9b8b578) to head (c0af523).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #317   +/-   ##
=======================================
  Coverage   44.67%   44.67%           
=======================================
  Files          79       79           
  Lines        7199     7199           
=======================================
  Hits         3216     3216           
  Misses       3983     3983           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

geoffjay added a commit that referenced this pull request Mar 11, 2026
- Replace ENTRYPOINT with CMD to prevent `claude claude` invocation
  when DockerBackend sets cmd via create_container (blocking issue)
- Remove unused `AS base` alias from single-stage build
- Pin @anthropic-ai/claude-code version via build ARG for reproducibility
- Remove redundant mkdir/chown after usermod --move-home
- Add fmt-fix and docker-build-claude-multiarch to .PHONY in Makefile
- Add comment noting arm64 verify gap in CI workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace ENTRYPOINT with CMD to prevent `claude claude` invocation
  when DockerBackend sets cmd via create_container (blocking issue)
- Remove unused `AS base` alias from single-stage build
- Pin @anthropic-ai/claude-code version via build ARG for reproducibility
- Remove redundant mkdir/chown after usermod --move-home
- Add fmt-fix and docker-build-claude-multiarch to .PHONY in Makefile
- Add comment noting arm64 verify gap in CI workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@geoffjay geoffjay merged commit d453804 into main Mar 11, 2026
10 of 11 checks passed
@geoffjay geoffjay deleted the issue-286 branch March 11, 2026 14:13
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.

Create Dockerfile and CI pipeline for Claude Code execution image

1 participant