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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Public container images for cloud work, built multi-arch and rootless with secur
| Image | Description |
|---|---|
| `ghcr.io/cloudsnacks/actions-runner` | Rootless GitHub Actions runner for [Actions Runner Controller](https://github.com/actions/actions-runner-controller) |
| `ghcr.io/cloudsnacks/claude-code` | Headless Claude Code agent with git-repo and agent-profile bootstrap |
| `ghcr.io/cloudsnacks/infisical-mcp` | Infisical MCP server (secrets management over MCP) |
| `ghcr.io/cloudsnacks/kubectl` | Rootless kubectl CLI |
| `ghcr.io/cloudsnacks/sandbox-agent` | Rootless base image for sandboxed coding agents (Node, Python, uv, git, gh, ripgrep) |
Expand All @@ -31,6 +32,33 @@ Every image is tagged `X.Y.Z`, `X.Y`, `X`, and `latest`.
- Base images pinned by digest, tool versions pinned and updated by Renovate
- SBOM and SLSA provenance attestations attached to every image

### claude-code

The entrypoint bootstraps the sandbox, then execs the container command (default `claude -p`). Everything is optional; with no variables set the agent starts in the empty `/workspace` scratch space.

| Variable | Effect |
|---|---|
| `AGENT_REPO` | Repo cloned into `AGENT_WORKSPACE` (skipped if it already contains `.git`) |
| `AGENT_REPO_REF` | Branch or tag to clone |
| `AGENT_PROFILE_REPO` | Repo of agent profiles, shallow-cloned at startup |
| `AGENT_PROFILE` | Subdirectory of that repo to install; defaults to the repo root |
| `AGENT_PROFILE_REF` | Branch or tag of the profile repo |
| `AGENT_PROMPT` | Prompt piped to the command on stdin; omit to pipe it in yourself |
| `AGENT_WORKSPACE` | Working directory (default `/workspace`) |
| `GITHUB_TOKEN` | Configures git to authenticate `https://github.com/` clones |

The profile's contents are copied into `CLAUDE_CONFIG_DIR` (`/home/agent/.claude`), so a profile directory holds `settings.json`, `agents/`, `skills/`, `CLAUDE.md`, and anything else Claude Code reads from there.

```shell
docker run --rm \
-e ANTHROPIC_API_KEY \
-e AGENT_REPO=https://github.com/cloudsnacks/containers.git \
-e AGENT_PROFILE_REPO=https://github.com/cloudsnacks/agent-profiles.git \
-e AGENT_PROFILE=reviewer \
-e AGENT_PROMPT="Summarise the CI workflow" \
ghcr.io/cloudsnacks/claude-code:latest
```

### Verifying provenance

```shell
Expand Down
24 changes: 24 additions & 0 deletions images/claude-code/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM ghcr.io/cloudsnacks/sandbox-agent:1.0.0@sha256:35ea6452c36f453785b340b21980ac81641edaffde67b51033159a30bbf9898a

# renovate: datasource=npm depName=@anthropic-ai/claude-code
ARG CLAUDE_CODE_VERSION=2.1.220

USER root

RUN NPM_CONFIG_PREFIX=/usr/local npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
&& npm cache clean --force \
&& claude --version

COPY --chmod=0755 entrypoint.sh /usr/local/bin/entrypoint

USER agent

ENV AGENT_WORKSPACE=/workspace \
CLAUDE_CONFIG_DIR=/home/agent/.claude

WORKDIR /workspace

LABEL org.opencontainers.image.description="Headless Claude Code agent with git-repo and agent-profile bootstrap"

ENTRYPOINT ["/usr/local/bin/entrypoint"]
CMD ["claude", "-p"]
38 changes: 38 additions & 0 deletions images/claude-code/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

workspace="${AGENT_WORKSPACE:-/workspace}"
profile_dir="${CLAUDE_CONFIG_DIR:-${HOME}/.claude}"

if [[ -n "${GITHUB_TOKEN:-}" ]]; then
git config --global \
"url.https://x-access-token:${GITHUB_TOKEN}@github.com/.insteadOf" \
"https://github.com/"
fi

if [[ -n "${AGENT_PROFILE_REPO:-}" ]]; then
checkout="$(mktemp -d)"
git clone --depth 1 ${AGENT_PROFILE_REF:+--branch "${AGENT_PROFILE_REF}"} \
"${AGENT_PROFILE_REPO}" "${checkout}"
source="${checkout}${AGENT_PROFILE:+/${AGENT_PROFILE}}"
if [[ ! -d "${source}" ]]; then
echo "agent profile not found in repo: ${AGENT_PROFILE:-<root>}" >&2
exit 1
fi
rm -rf "${checkout}/.git"
mkdir -p "${profile_dir}"
cp -aT "${source}" "${profile_dir}"
rm -rf "${checkout}"
fi

mkdir -p "${workspace}"
if [[ -n "${AGENT_REPO:-}" && ! -d "${workspace}/.git" ]]; then
git clone ${AGENT_REPO_REF:+--branch "${AGENT_REPO_REF}"} "${AGENT_REPO}" "${workspace}"
fi
cd "${workspace}"

if [[ -n "${AGENT_PROMPT:-}" ]]; then
exec "$@" <<<"${AGENT_PROMPT}"
fi

exec "$@"
9 changes: 9 additions & 0 deletions images/claude-code/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
name: claude-code
description: Headless Claude Code agent with git-repo and agent-profile bootstrap
# renovate: datasource=npm depName=@anthropic-ai/claude-code
version: 2.1.220
platforms:
- linux/amd64
- linux/arm64
test: claude --version && node --version && git --version && test "$(pwd)" = /workspace