The container image#
The image is the half of podbench that runs in the cluster. It is a development seat, not a CLI wrapper: a developer lands inside it over ssh, so the whole toolchain has to be there.
Note
Commands here are written podbench <verb> — the only spelling there is. If you
have not installed the launcher, run each as uvx podbench <verb>. See
Setup.
ghcr.io/gilesknap/podbench:<launcher version>
Built and pushed by CI on tag; a numbered release tag pins a specific build.
The default tag follows the launcher’s own version, so a launcher asks for
the image built from its own source. That matters because uvx podbench resolves
a launcher afresh on every run, and a version can move between two attaches with
nothing to see: pinning the tag to the version keeps the two halves in step,
where a fixed :latest would eventually let a launcher author a container spec
its image does not understand.
A launcher built from a checkout — a clone, or uvx --from git+... — matches no
published image and falls back to :main, the branch-tip image CI pushes on
every default-branch commit. That is the same-source counterpart to such a
launcher. :latest is deliberately not the fallback: it moves only on a
final release, so an unpinned user is never handed a prerelease — but for
the same reason it does not move at all on a project that has so far tagged only
prereleases, and a stale :latest is exactly the launcher/image skew this
scheme exists to prevent.
One release has two spellings, and CI pushes both onto the same digest: the git
tag and the chart use SemVer (1.0.0-beta.1), while the wheel — and so the
launcher’s version — uses PEP 440 (1.0.0b1). Either tag pulls the same image.
Note
Tags published before this scheme (0.1.0-alpha.1 … 0.1.0-alpha.6) carry the
SemVer spelling only. A launcher installed from one of those git tags asks for
its own PEP 440 spelling — 0.1.0-alpha.4 becomes 0.1.0a4 — which was never
pushed; pass the SemVer tag of that same release, e.g. --image ghcr.io/gilesknap/podbench:0.1.0-alpha.4.
You normally never pull it yourself. podbench attach names it in the
ephemeral container spec and the kubelet pulls it onto whichever node the target
pod is running on.
Choosing a different image#
$ podbench attach pod/foo --image ghcr.io/gilesknap/podbench:0.3.0
$ export PODBENCH_IMAGE=registry.internal/podbench@sha256:...
--image wins over PODBENCH_IMAGE, which wins over the default.
Pin a digest for anything permanent. An admission policy that says “this
image, as an ephemeral container only, with only SYS_PTRACE, for these users”
is only writable against a pinned, published image — and that policy is the
whole organisational argument for allowing podbench at all. See
Security model.
The Helm chart records the same reference under image.repository / image.tag
so a cluster has one place to state which build it trusts. Nothing in the chart
templates it — it is there to be read by the admission policy you write — so a
cluster that pins there must pin the launcher too, with PODBENCH_IMAGE or
--image. Left alone, the launcher tracks its own version and the chart’s
image.tag stays empty.
Mirroring it#
Nothing in podbench requires ghcr.io specifically:
$ skopeo copy docker://ghcr.io/gilesknap/podbench:0.3.0 \
docker://registry.internal/podbench:0.3.0
$ export PODBENCH_IMAGE=registry.internal/podbench:0.3.0
If your registry needs credentials, the target pod’s namespace needs the
imagePullSecrets — the ephemeral container is pulled with the pod’s service
account, like any other container.
Running it on your laptop#
You can, but there is very little point: outside a pod there is no target container, no shared PID namespace and nothing to debug. It is useful for exactly two things — checking a tag exists, and inspecting what is inside:
$ docker run --rm ghcr.io/gilesknap/podbench:latest --version
$ docker run --rm -it --entrypoint bash ghcr.io/gilesknap/podbench:latest
The entrypoint is podbench, so arguments are podbench verbs. In a pod the
launcher overrides the command with podbench agent, which prepares sshd and
then idles as PID 1.
What is in it#
debian:bookworm-slim (glibc 2.36), because vscode-server needs glibc ≥ 2.28
— Alpine and musl are unsupported, and the real failure there is a missing ELF
interpreter rather than symbol versions. It is also the base of
gcr.io/distroless/*-debian12, the most common Observe-mode target, which makes
build IDs and -dbgsym packages line up. Treat that as a convenience only:
set sysroot /proc/<pid>/root is what makes gdb correct, and a matched distro
hides the wrong-sysroot bug rather than fixing it.
Group |
Contents |
|---|---|
Connection |
|
TLS |
|
Debugging |
|
Inspection |
|
Iteration |
|
PID 1 |
|
Roughly 450–500 MiB uncompressed, inside the 700 MiB budget the design brief sets.
Three deliberate omissions:
vscode-server — the client/server version check is a hard handshake rejection, so a baked server is correct for about four weeks. It downloads on first connect in 2.17 s.
ssh host keys — a private key baked into a published image is the same private key on every pod in the world. They are minted per attach.
A compiler — the ptrace probe uses the bundled interpreter’s
ctypes, so ~200 MiB of toolchain is not carried.
gdb keeps its Python. Debian’s gdb 13 hard-fails when its Python stdlib is
missing rather than degrading, and installing it from apt solves that by
construction. Do not prune /usr/lib/python3.11 to save space.
Helpers on PATH#
Two, and both are structural rather than convenient.
On |
What it is |
|---|---|
|
|
|
installed as |
Every in-pod verb is reached as podbench <verb>: podbench pids, podbench dbg, podbench capreport, podbench debug-config, podbench dev-bootstrap,
podbench run, podbench stop. There are no shorter aliases — podbench --help lists the lot.
Environment it reads#
Variable |
Meaning |
|---|---|
|
authorized key, injected by the launcher |
|
read it from a file instead (default mount |
|
supply a host key rather than minting one; the file default is |
|
the target container’s runtime ID, injected at attach time; how |
|
the target container’s name, injected at attach time; what |
|
every container in the pod, comma-separated, injected at attach time; how |
|
defaults to |
|
seconds gdb will wait on that server, per file. Defaults to |
sshd passes none of its own environment to the commands it runs, so podbench agent names the ones that matter in the sshd config it generates: every
PODBENCH_* variable except the keys, plus PATH, DEBUGINFOD_URLS and
DEBUGINFOD_TIMEOUT. Anything else you set on the container reaches kubectl exec and a shell, and not an ssh session. If a value contains whitespace sshd
cannot carry it, and the agent says so in the container’s start-up log rather
than dropping it quietly — kubectl logs <pod> -c <the debug container>.
Building it yourself#
$ docker build -t podbench:dev .
The repository Dockerfile has developer → build → runtime stages; the
image is the runtime stage. To use a local build against a cluster you must
push it somewhere the cluster can pull from — the kubelet pulls it, so a local
daemon image is not enough unless your cluster shares that daemon (kind:
kind load docker-image podbench:dev).