A batteries-included container image for CLI coding agents.
Agents that work in a terminal spend most of their time shelling out — grepping a
repo, converting a video, running a test suite, formatting Python. climage
packages that surface area once: an official Node.js base, a complete Python
toolchain built on uv and ruff, and the command-line utilities agents reach
for by reflex. Point your agent at this image instead of installing tooling on
first run.
This repository contains the image definition and the CI pipeline that publishes it to Docker Hub. There is no application code.
| Area | Tools |
|---|---|
| Runtimes | node, npm, npx, pnpm, corepack, python3 (Debian), uv-managed CPython |
| Python toolchain | uv, uvx, ruff |
| Go toolchain | go, gofmt — off by default, in the full and full-argus variants |
| Rust toolchain | rustc, cargo, rustup, clippy, rustfmt, cargo audit — off by default, in the full and full-argus variants |
| Kubernetes | kubectl, with kubectl kustomize built in, helm with the helm diff plugin, and helmfile — off by default, in the full and full-argus variants |
| Search & text | rg (ripgrep), fd, bat, jq, tree, file, less, diff, patch, moreutils |
| Media & documents | ffmpeg, ffprobe, convert (ImageMagick), pdftotext (poppler) |
| VCS & network | git, git-lfs, gh, ssh, curl, wget, rsync, dig, ping, nc, socat |
| Secrets & hooks | gitleaks, pre-commit |
| Linters | hadolint (Dockerfiles), actionlint (GitHub Actions workflows), shellcheck |
| Data | sqlite3 |
| Build | build-essential (gcc, make), pkg-config |
| Shell & process | bash, tmux, vim.tiny, htop, procps, tini |
| Archives | unzip, zip, xz, bzip2, tar, gzip |
| Agent CLIs | claude (Claude Code), codex (OpenAI Codex), skills (open agent-skills manager) — all optional, on by default |
| First-party | argus-sidecar — off by default, only in the full-argus variant |
python and python3 resolve to the uv-managed CPython (3.12 by default), not
Debian's system interpreter — the pinned version is what runs, whichever name an
agent types.
Runs as the unprivileged climage user (uid 1000, in the stock users group,
gid 100 — there is no per-user group) with /workspace as the
working directory. tini is PID 1 so long-lived agent sessions reap their children. The
image is designed to be extended at runtime without root: uv tool install,
npm install -g and pnpm add -g all work as the climage user, in interactive
and login shells, and all write under $HOME — so a volume mounted at
/home/climage carries whatever an agent installs across restarts.
pnpm is pinned like everything else, but a project whose package.json names
another version in its packageManager field gets that one: pnpm fetches it on
first use and runs it for that project only. Yarn is not included — the build removes the Yarn
Classic the official Node.js image ships, so a stray yarn cannot write a
second lockfile into a pnpm project. corepack, which comes with Node.js, is
present but not enabled: corepack yarn and corepack pnpm run the version a
project's packageManager field names, with no shims on PATH.
The default command adapts to how the container was started: a terminal gets an interactive shell, piped stdin is run as a script, and a container with neither (a Kubernetes pod, a detached container) parks so you can exec into it. Passing your own command bypasses it entirely.
- Docker 24+ with BuildKit (Docker 29 tested)
docker buildxfor multi-architecture builds (bundled with Docker Desktop and recent Docker Engine)- Outside the image only, since every climage variant ships them:
pre-commit 3.2.0 or later and
hadolintfor the commit hooks, andshellcheckandgitleaksformake lint(optional). The hadolint hook runs whichever release is onPATH; CI lints with the one inHADOLINT_VERSION
Pull the published image and drop into a shell with your project mounted:
docker run --rm -it -v "$PWD:/workspace" kr4t0n/climageRun a one-off command:
docker run --rm -v "$PWD:/workspace" kr4t0n/climage rg -n "TODO"If your host uid is not 1000, mounted files will be owned by a different user inside the container. Run as yourself to keep write access:
docker run --rm -it --user "$(id -u):$(id -g)" -v "$PWD:/workspace" kr4t0n/climagemake build # build for the host architecture, tagged climage:dev
make test # build, then run tests/smoke.sh inside the image
make shell # interactive shell with $PWD mounted at /workspace
make run CMD="uv --version"
make lint # hadolint + shellcheck + gitleaks
make size # report the image size
make push # multi-arch build + push to Docker Hub (CI normally does this)
make help # list all targetsEvery target takes VARIANT, which mirrors the CI matrix and defaults to
base. Each variant builds to its own tag (climage:dev, climage:dev-slim,
climage:dev-full, climage:dev-full-argus), so one does not overwrite another:
make test VARIANT=full # build the full variant and smoke-test it
make size VARIANT=slim
make test-all # build and smoke-test all four, as CI doesVersion pins come from the Dockerfile unless you override one explicitly:
make build GO_VERSION=1.26. CONFIGURATION.md
lists every build argument.
Four variants are published. latest is the one to use unless you need
something it does not have.
| Tag | Contents | Uncompressed | Compressed (registry) |
|---|---|---|---|
latest |
everything in the table above except the Go and Rust toolchains and the Kubernetes tools | 2.2 GB | 809 MB |
slim |
no media or build-tool packages | 1.5 GB | 574 MB |
full |
latest plus the Go and Rust toolchains, the Kubernetes tools and the headless-browser libraries |
3.4 GB | 1214 MB |
full-argus |
full plus first-party tooling (argus-sidecar) |
3.4 GB | 1219 MB |
Both columns are amd64: uncompressed is the total size of the image's files,
compressed is what Docker Hub reports for the pushed image. arm64 runs slightly
smaller in both.
The Go and Rust toolchains account for most of the difference between full
and latest (405 MB compressed), and the Kubernetes tools for 115 MB of it,
helmfile alone 47 MB. Pull latest unless you need them.
docker pull kr4t0n/climage:fulllatest, slim, full and full-argus follow main and move with every
push. A release also publishes version tags — 0.4.0 and 0.4, plus the
-slim, -full and -full-argus suffixed equivalents — and every build is
addressable by a short-SHA tag. Pin a version tag when you need the base image
to stay put.
Each variant is a set of build arguments, listed under
Variants with the matching docker build commands.
Both full variants ship the system libraries Chromium needs, so Playwright and
Puppeteer work as the unprivileged user with no apt and no root:
docker run --rm kr4t0n/climage:full bash -lc \
'npm i playwright && npx playwright install chromium && node my-script.js'Only the libraries are baked in, not a browser binary: playwright install
fetches the build that matches your client library into ~/.cache/ms-playwright,
which a volume mounted at /home/climage will persist.
On latest or slim the same script fails when Chromium starts. Installing the
libraries at runtime needs root and an apt-get update first, because the
image ships no package index:
docker exec -u 0 <container> bash -lc 'apt-get update && apt-get install -y \
--no-install-recommends libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 \
libxcomposite1 libxdamage1'Both full variants ship kubectl, with kubectl kustomize built in. It is
pinned to one Kubernetes minor (KUBECTL_VERSION, 1.35 at present), and kubectl
supports API servers one minor older or newer than itself, so check your
cluster's version before relying on it. The image carries no kubeconfig and no
credentials: kubectl reads ~/.kube/config or KUBECONFIG as usual, and a
volume mounted at /home/climage persists both the config and kubectl's cache.
They also ship Helm 3 (HELM_VERSION, 3.21 at present) with the
helm-diff plugin, and
helmfile. helm diff upgrade renders
the chart on the client by default, and helmfile diff and helmfile apply use
it. The plugin lives outside $HOME, in the directory HELM_PLUGINS names, so
a home volume cannot hide it. Everything else helm keeps, such as repositories
and the chart cache, stays under $HOME and persists with the volume. To add
another plugin, see Helm plugins.
The skills CLI installs skills from
the open agent-skills ecosystem into whichever agents are present:
skills add vercel-labs/agent-skills --list # browse a source
skills add vercel-labs/agent-skills --skill frontend-design \
--global --agent claude-code --agent codex --yes # non-interactive
skills list --globalSkills install to a canonical ~/.agents/skills/<name>/ and are symlinked into
each agent's own directory (~/.claude/skills/, and Codex's universal location).
Because that all lives under $HOME, a volume mounted at /home/climage
persists installed skills along with agent credentials. Skills execute with full
agent permissions, so review a source before installing it.
CONFIGURATION.md lists every build argument, runtime environment variable and publishing credential. The ones most people touch:
INSTALL_*build arguments choose what goes into an image (the variants are combinations of them), and the*_VERSIONarguments pin each tool.CLIMAGE_INITnames a script the entrypoint sources before your command, for per-container bootstrap.DOCKERHUB_USERNAMEandDOCKERHUB_TOKEN, in.env, letmake pushpublish; CI reads them from GitHub secrets.
Both agent CLIs can replace themselves with a newer release than the image
pins. The image turns off every update that starts without being asked for:
Claude Code's with DISABLE_AUTOUPDATER=1, Codex's startup prompt and
background daemon in /etc/codex/config.toml. claude update and
codex update, run by hand, still work.
Agent CLI updates sets out both CLIs the
same way: what updates, how it is off, what is still open, how to turn it back
on, and how to check.
.github/workflows/ci.yml runs on every pull request to main, every push to
main, and every v* tag:
- Lint and secret scan — hadolint and shellcheck, and gitleaks over the full git history.
- Build and smoke test — all four variants, each on a native amd64 and a native arm64 runner, with the image size reported.
- Publish — pushes to
mainandv*tags only. Each build is pushed to Docker Hub by digest, then assembled into the multi-arch tags described under Image variants.
A pull request builds and tests exactly what merging it would publish.
.
├── Dockerfile # the image definition (single source of truth)
├── README.md # this overview
├── CONFIGURATION.md # build args, runtime env vars, agent CLI updates
├── AGENTS.md # design decisions, conventions and gotchas
├── CLAUDE.md # points Claude Code at AGENTS.md
├── .dockerignore # keeps the build context to scripts/ only
├── .hadolint.yaml # Dockerfile lint rules
├── Makefile # build / test / run / push helpers
├── .env.example # template for local publishing credentials
├── .pre-commit-config.yaml # gitleaks + hadolint + shellcheck + hygiene hooks
├── scripts/
│ ├── entrypoint.sh # workspace checks, optional init hook, exec
│ └── climage-idle.sh # the default CMD: shell, script, or park
├── tests/
│ └── smoke.sh # tool inventory assertions, run inside the image
└── .github/
├── dependabot.yml # weekly base-image and action bumps
└── workflows/ci.yml # lint & secret scan -> build & test -> publish
Enable the commit hooks once per clone. Every climage variant ships pre-commit; outside the image it is a prerequisite.
pre-commit installThe hooks scan staged changes for secrets with gitleaks, lint the Dockerfile
with the hadolint on PATH, and lint the scripts with shellcheck through
Docker. In a container with no Docker daemon, skip shellcheck with
SKIP=shellcheck; CI runs both linters on every pull request, and repeats the
secret scan over the whole history. Inside the image the linters need no
Docker: make lint runs the image's own copies (slim has no make, so run
its three commands directly), and the image's hadolint, which the hook runs
too, is the release CI uses.
A finding that is not a secret is allowlisted in .gitleaks.toml or
.gitleaksignore and reviewed like any other change.
Commits follow Conventional Commits.
Adding a tool means updating three places: the Dockerfile package list, the
REQUIRED array in tests/smoke.sh, and the What's inside
table. A tool that brings a build argument (a version pin or an INSTALL_*
flag) also gets a row in CONFIGURATION.md.
AGENTS.md holds the design decisions and gotchas to read before
changing the Dockerfile or CI.