anvilsign in

collin/anvil

1# anvil-runner — the shared execution image for BOTH CI jobs and agent
2# sessions.
3#
4# CI pipelines that omit `image:` land here (config `ci.default_image`), and
5# agent sessions always do (`agent.image`). Keeping them the same image means a
6# session can reproduce a build by hand, and there is one thing to keep current.
7#
8# Parameterized by base so the same recipe produces a small general runner and
9# a toolchain-carrying one:
10#
11# anvil-runner:latest BASE=debian:bookworm-slim (the default)
12# anvil-runner:rust BASE=rust:1.95-bookworm (builds anvil itself)
13#
14# Build both with deploy/runner/build.sh. There is NO registry behind this
15# image — it lives only in the host's local image store, which is why anvil
16# treats a failed pull of the default image as non-fatal.
17ARG BASE=debian:bookworm-slim
18FROM ${BASE}
19
20# Which Claude Code release channel to track. `stable` is roughly a week behind
21# and skips releases with known major regressions; `latest` ships immediately.
22ARG CLAUDE_CHANNEL=stable
23
24ENV DEBIAN_FRONTEND=noninteractive
25
26# Fingerprint of the Claude Code release signing key, checked below so a
27# substituted key fails the build rather than silently installing. Published at
28# https://code.claude.com/docs/en/setup. Deliberately inlined rather than an
29# ARG: a build arg could be overridden on the command line, which would defeat
30# the pin it exists to enforce.
31
32# tmux — session persistence; the whole point of the agent design
33# git — the container clones/pushes for itself (anvil's own code never
34# shells out to git, but what a job runs is its own tooling)
35# fish — the interactive shell you actually get when you attach
36# ripgrep — Claude Code's search backend
37# curl/gnupg/ca-certificates — fetching and verifying the apt repo key
38RUN apt-get update \
39 && apt-get install -y --no-install-recommends \
40 ca-certificates \
41 curl \
42 fish \
43 git \
44 gnupg \
45 less \
46 ripgrep \
47 tmux \
48 && install -d -m 0755 /etc/apt/keyrings \
49 && curl -fsSL https://downloads.claude.ai/keys/claude-code.asc \
50 -o /etc/apt/keyrings/claude-code.asc \
51 && gpg --show-keys --with-colons /etc/apt/keyrings/claude-code.asc \
52 | awk -F: '/^fpr:/ {print $10}' \
53 | grep -qx 31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE \
54 && echo "deb [signed-by=/etc/apt/keyrings/claude-code.asc]" \
55 "https://downloads.claude.ai/claude-code/apt/${CLAUDE_CHANNEL}" \
56 "${CLAUDE_CHANNEL} main" > /etc/apt/sources.list.d/claude-code.list \
57 && apt-get update \
58 && apt-get install -y --no-install-recommends claude-code \
59 && rm -rf /var/lib/apt/lists/*
60
61# apt installs never auto-update, but Claude Code still checks on startup and
62# the check is pure noise in a container whose version is pinned by the image.
63ENV DISABLE_AUTOUPDATER=1
64
65# An unprivileged user for agent sessions to run as. Deliberately NOT set as
66# the image's USER: CI pipelines inherit this image's default user, and plenty
67# of them expect root for apt-get. anvil passes `user: agent` explicitly when
68# it creates a *session* container, so CI keeps the behaviour it has today.
69RUN useradd --create-home --shell /usr/bin/fish --uid 1000 agent \
70 && mkdir -p /workspace \
71 && chown agent:agent /workspace
72
73COPY tmux.conf /etc/anvil/tmux.conf
74COPY session-entrypoint.sh /usr/local/bin/anvil-session
75RUN chmod 0755 /usr/local/bin/anvil-session
76
77WORKDIR /workspace