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
20 changes: 20 additions & 0 deletions .circleci/claude-code-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2.1
orbs:
architect: giantswarm/architect@6.12.0

workflows:
build-claude-code-ci-default:
jobs:
- architect/push-to-registries:
context: architect
name: push-to-registries
git-tag-prefix: claude-code-ci
tag-suffix: ""
image: giantswarm/claude-code-ci
dockerfile: ./claude-code-ci/default.dockerfile
build-context: claude-code-ci
filters:
tags:
only: "/^claude-code-ci.*/"
branches:
ignore: main
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
We use dates instead of semantic versions.

## 2026-02-07

### Added

- `claude-code-ci` image based on `node:24-alpine` with Claude Code, dev tools (git, gh, jq, ripgrep, fd, bat, etc.), and Python tools (uv, tldr)
- `claude-code-ci` custom entrypoint with CI defaults: `--print`, `--allow-dangerously-skip-permissions`, `--model claude-haiku-4-5-20251001`, `--max-turns 10`, `--max-budget-usd 0.20`, `--verbose`; all overridable via env vars

## 2025-12-08

### Changed

- `apache2-utils`: added arm64 architecture for use in mc-bootstrap on ARM

## 2025-10-07

### Added

- `yamllint` image based on Python 3.13 Alpine
- `alpine-bats` image for giantswarm/helmclient integration tests

## 2025-08-28

### Changed

- Switched base image registry from quay.io to gsoci.azurecr.io

## 2025-03-04

### Added

- `apache2-utils` image

## 2024-10-29

### Changed

- Updated base image for `calico-crd-installer`
- Corrected pipelines for `awscli-tar` and `calico-crd-installer`
- Updated images to the latest versions done by retagger
- Bumped `dynamic-continuation` orb to v3.9.1

## 2024-10-04

### Changed

- Updated `alpine/envsubst` Dockerfile
3 changes: 3 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ alpine/bats.dockerfile @giantswarm/team-honeybadger
./circleci/apache2-utils.yml @giantswarm/team-honeybadger
apache2-utils @giantswarm/team-honeybadger

.circleci/claude-code-ci.yml @giantswarm/team-honeybadger
claude-code-ci/default.dockerfile @giantswarm/team-honeybadger

.gitignore @giantswarm/team-honeybadger
README.md @giantswarm/team-honeybadger
add-new-image.sh @giantswarm/team-honeybadger
Expand Down
127 changes: 127 additions & 0 deletions claude-code-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# claude-code-ci

Claude Code for use in CI/CD pipelines.

## TODO

- Add GitHub PR details

## Defaults

The entrypoint always applies `--print` and `--allow-dangerously-skip-permissions`. All other defaults are configurable via environment variables.

## Environment variables

Use these environment variables to configure the execution of `claude`.

| Variable | Default | CLI flag | Description |
|----------|---------|----------|-------------|
| `CLAUDE_PROMPT` | **(required)** | positional | The prompt to send to Claude |
| `CLAUDE_ALLOWED_TOOLS` | *(empty)* | `--allowedTools` | Comma-separated list of allowed tools |
| `CLAUDE_MAX_BUDGET` | `0.20` | `--max-budget-usd` | Maximum spend in USD |
| `CLAUDE_MAX_TURNS` | `10` | `--max-turns` | Maximum agentic turns |
| `CLAUDE_MODEL` | `claude-haiku-4-5-20251001` | `--model` | Model to use |
| `CLAUDE_OUTPUT_FORMAT` | `stream-json` | `--output-format` | Output format (`text`, `json`, `stream-json`) |
| `CLAUDE_TOOLS` | `default` | `--tools` | Available tools (`default`, `""`, or tool names) |
| `CLAUDE_VERBOSE` | `1` | `--verbose` | Verbose logging (`1` = on, `0` = off) |

Any additional arguments passed to the container are forwarded directly to `claude`.

Depending on the use, you might have to permit specific tool use via the `--allowedTools` flag.

## Testing

`$TAG` must be an existing tag.

```bash
# With defaults
docker run \
gsoci.azurecr.io/giantswarm/claude-code-ci:$TAG \
"explain this code"

# With overrides
docker run \
-e CLAUDE_MODEL=sonnet \
-e CLAUDE_MAX_BUDGET=1.00 \
gsoci.azurecr.io/giantswarm/claude-code-ci:$TAG \
"review this PR"

# Pass extra flags
docker run \
gsoci.azurecr.io/giantswarm/claude-code-ci:$TAG \
--output-format json \
"summarize this repo"
```

## Usage in a GitHub workflow

Here is an example.

```yaml
name: Auto-update changelog

on:
pull_request: {}

permissions: {}

jobs:
report:
runs-on: ubuntu-latest
permissions:
contents: write # To push commits
pull-requests: write # To comment in PR
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # We need the full history to compare changes
persist-credentials: true # For the next step

- name: Update changelog
uses: docker://gsoci.azurecr.io/giantswarm/claude-code:v0.0.1
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: |
--allowedTools "Edit(/github/workspace/CHANGELOG.md)" \
--allowedTools "Bash(git *)" \
"You are executed in a Github action runner, in the context of a pull request.
Environment variables give you information about the repository etc.
You have the gh CLI available.

Your task: If a CHANGELOG.md file exists in the repository root, check if the current pull
request updates it.

If CHANGELOG.md exists, but is not updated in this PR, update it with information
about the changes in this branch compared to the default branch. Push a simple commit
to the PR's branch. Do not rebase.

Normally, dependency updates fall under the '### Changed' category. In some cases,
if they are security-related, they might fall under the '### Fixed' category.

Use the pull request title and description for hints. Use `git diff` to find out details about file changes.

Do nothing else. Do not recommend next actions. Finish the given task in one step.

---------------------------------
Pull request details:

- **Title:** '${{ github.event.pull_request.title }}'
- **Author:** '${{ github.event.pull_request.user.login }}' (${{ github.event.pull_request.user.name }})
- **Branch:** '${{ github.head_ref }}'
- **Base branch:** ${{ github.event.pull_request.base.ref }}
- **Repository:** ${{ github.repository }}
- **PR number:** ${{ github.event.pull_request.number }}

**Description:**

${{ github.event.pull_request.body }}

**Changed files:**

${{ join(github.event.pull_request.changed_files, ', ') }}

---------------------------------"
```
45 changes: 45 additions & 0 deletions claude-code-ci/default.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
FROM --platform=linux/amd64 node:24.13.0-alpine3.23

RUN apk add --no-cache \
bash \
git \
github-cli \
curl \
wget \
python3 \
py3-pip \
build-base \
jq \
ripgrep \
fd \
bat \
tree \
httpie \
rsync \
shellcheck

# Install Claude Code globally
RUN npm install -g @anthropic-ai/claude-code@2.1.36

# Install Python tools
RUN pip3 install \
--break-system-packages \
uv==0.10.0 \
tldr==3.4.4

# Create a non-root user
RUN adduser -s bash -D user

# Create necessary directories
RUN mkdir -p /home/user/.config/claude-code \
&& mkdir -p /home/user/.local/bin \
&& chown -R user:user /home/user

# Copy entrypoint script
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh

# Switch to user
USER user
WORKDIR /home/user

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

# CI defaults — override via environment variables
CLAUDE_MAX_BUDGET="${CLAUDE_MAX_BUDGET:-0.20}"
CLAUDE_MAX_TURNS="${CLAUDE_MAX_TURNS:-10}"
CLAUDE_MODEL="${CLAUDE_MODEL:-claude-haiku-4-5-20251001}"
CLAUDE_OUTPUT_FORMAT="${CLAUDE_OUTPUT_FORMAT:-stream-json}"
CLAUDE_TOOLS="${CLAUDE_TOOLS:-default}"
CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-}"
CLAUDE_VERBOSE="${CLAUDE_VERBOSE:-1}"

# test if CLAUDE_PROMPT is set, otherwise exit with an error
if [ -z "${CLAUDE_PROMPT:-}" ]; then
echo "Error: CLAUDE_PROMPT environment variable is not set" >&2
exit 1
fi

args=(
--max-budget-usd "$CLAUDE_MAX_BUDGET"
--max-turns "$CLAUDE_MAX_TURNS"
--model "$CLAUDE_MODEL"
--output-format "$CLAUDE_OUTPUT_FORMAT"
--permission-mode "dontAsk"
--print
--tools "$CLAUDE_TOOLS"
)

if [ -n "$CLAUDE_ALLOWED_TOOLS" ]; then
IFS=',' read -ra ALLOWED <<< "$CLAUDE_ALLOWED_TOOLS"
for tool in "${ALLOWED[@]}"; do
tool="$(echo "$tool" | xargs)" # trim whitespace
args+=(--allowedTools "$tool")
done
fi

if [ "$CLAUDE_VERBOSE" = "1" ]; then
args+=(--verbose)
fi

exec claude "${args[@]}" "$CLAUDE_PROMPT"