Skip to main content
Clawker generates and builds your project’s Docker images itself. There is no user-supplied base image and no custom Dockerfile — every image starts from a pinned Debian substrate, and all customization flows through the build block of .clawker.yaml: system packages, language stacks, typed build instructions, and raw Dockerfile injection points.

The Two-Stage Build

Each project gets a shared base image plus one harness image per coding-agent harness you build: The base image starts from a pinned debian:bookworm-slim digest and layers in the development floor every container gets: git, zsh, sudo, gh, curl, wget, jq, fzf, make, gcc, editors (nano, vim), locales, and the Docker CLI (for socket-mount workflows). Your build.packages install into the same apt layer. The harness image adds whatever the selected harness declares — for example, claude installs Claude Code (plus the node stack it runs on), while codex installs the standalone Codex binary — and ends with ENTRYPOINT ["clawkerd"] and the harness’s CMD.

Caching

The base image is keyed by a content hash of its inputs (the rendered base Dockerfile plus the contents of any instructions.copy sources), stamped as an image label. Rebuilding a harness image reuses the existing base whenever that hash is unchanged — editing source files outside your copy sources never triggers a base rebuild. Within each stage, Docker’s normal layer cache (BuildKit or classic) skips unchanged layers. Use clawker build --no-cache to force a full rebuild.

Building and Tags

Tags are harness-keyed: -t NAME builds that harness, and the resulting tag is the harness name. NAME is a bare name for a built-in or loose harness, or a qualified namespace.bundle.component address for a harness from an installed bundle (see Harnesses). Building the default harness also stamps the :default alias. The default is the built-in claude harness unless the build.harness key selects another (see Setting the default harness). The @ shortcut in run/create commands resolves against these tags:
Images and containers carry the dev.clawker.harness label identifying which harness they were built for. Build-time variables can be overridden with --build-arg on clawker build — for example --build-arg CLAUDE_CODE_VERSION=2.1.4 pins the claude harness’s install to an exact version, or --build-arg NODE_VERSION=22 pins a stack’s runtime line. When a --build-arg targets an ARG the base image declares, Clawker folds the value into the base freshness key so changing it rebuilds the base (a build arg the base doesn’t declare — harness-only or unknown — never triggers a base rebuild).

System Packages

Install additional Debian (apt) packages with the packages field:

Stacks

Stacks are reusable language-runtime definitions. Declaring one installs it in the shared base image, before your root_run/user_run instructions execute — so your instructions can rely on it:
Clawker ships go, node, python, rust, java, ruby, cpp, and dotnet stack definitions. Each entry is a component name — bare for a built-in or loose stack, or a qualified namespace.bundle.component address for a bundled one. See Stacks for where stacks come from and how to author your own. Stacks are self-guarding — each skips itself when the image already provides the runtime. A project-declared stack renders in the shared base before your root_run/user_run; a harness-declared stack (for example, claude declares node) renders in the harness image. Both strata render even when they share a name — the fragment self-guards handle any overlap.

Build Instructions

The instructions block provides type-safe build directives. All of these render in the shared base image.

env

Environment variables baked into the image:

copy

Copy files from your project into the image with optional chown/chmod:
Copy sources are part of the base image’s content hash — editing a copied file triggers a base rebuild; editing other project files does not. copy steps render after user_run so expensive setup layers stay cached when a copied file changes.

labels

Add metadata labels to the image:

args

Define build-time arguments (override with --build-arg at build time):

root_run and user_run

Run commands during the build. root_run executes as root, before the container user is created; user_run executes as the container user, after the user switch. Both run after any project-declared stacks, so go, node, python, etc. are available:
user_run commands execute under /bin/zsh -o pipefail -c, not /bin/sh — clawker bakes zsh as the build shell so its shell tooling is available. Two consequences for scripts written against POSIX sh: zsh does not word-split unquoted variable expansions (quote your variables), and pipefail is active (a failing command anywhere in a pipe fails the build). root_run runs earlier in the build and is unaffected — it still uses /bin/sh.

Injection Points

For advanced use cases, the inject field provides raw Dockerfile injection points. Each field accepts an array of raw Dockerfile lines. Four points render in the base image, two in the harness image:
Injection point order across the generated build: Base image (clawker-<project>:base):
  1. FROM the pinned substrate
  2. after_from — apt sources, proxy config, CA certs that package installation depends on
  3. Package installation (Clawker’s floor + your build.packages)
  4. after_packages — post-install configuration as root
  5. Project-declared stacks (root scope), then your root_run
  6. Container user creation
  7. after_user_setup — directories, permissions, services (still root)
  8. USER switch to the unprivileged container user (clawker)
  9. after_user_switch — dotfiles, shell config
  10. Project-declared stacks (user scope), then your user_run, then copy
Harness image (clawker-<project>:<harness>, built FROM the base):
  1. Harness-declared stacks + the harness’s install steps (e.g. the harness CLI)
  2. user_commands — runs as the container user, after the harness’s fragment blocks and config seeds: add MCP servers, install plugins or extensions
  3. before_entrypoint — final tweaks before Clawker’s runtime assets and ENTRYPOINT
Because user_commands and before_entrypoint live in the harness image, they run once per harness you build — but the same build.inject applies project-wide, so a step that registers an MCP server with claude runs in every harness image you build. To scope an inject step to a single harness, use a per-harness overlay (below).

Per-Harness Overlays

The base build block applies to every image you build. To layer extra stacks, packages, or inject steps onto one harness’s image without touching the harness definition or the base, declare them under build.harnesses.<name> — the same stacks, packages, and inject primitives, scoped to that harness’s lineage. The <name> key uses the same spelling you select the harness by: a bare name for a built-in or loose harness, or a qualified namespace.bundle.component address for a bundled one.
  • stacks render in that harness’s image after the harness’s own stacks (installer → overlay, declaration order). A name that appears in both the harness and the overlay renders once, at its installer position.
  • packages install as an apt step in that harness’s image. They are not deduplicated against build.packages — apt idempotence handles any overlap.
  • inject exposes only the two harness-image points, user_commands and before_entrypoint. Overlay inject renders after any global build.inject at the same anchor, and only in that harness’s image — this is how you scope a claude mcp add to the claude container instead of every harness.
An overlay keyed to a harness that resolves nowhere — not a built-in, not a loose convention directory, and not an installed bundle — is a build error, so a typo’d overlay never silently does nothing.

Complete Example