Adding a vendor CLI to an image
Four ready build files, the three properties they all share, and what each CLI does behind a proxy — because two of the four ignore the variable most people reach for first.
For whoever builds the images a fleet dispatches onto.
After reading it you can put any supported vendor CLI into any class image, pin its version, and know how that CLI behaves behind a proxy before you put it behind one.
Before you start: a container runtime and the class image you are building on.
One build file per vendor, and they work on any class
The product ships one build file per supported vendor. Each takes the base image as a build argument and works unchanged on any of the six classes, because every class now carries the same Debian and the same Node.
| vendor | shape | version |
|---|---|---|
| Claude Code | npm package | pinned, overridable at build time |
| Qwen Code | npm package — needs Node 22 or newer | pinned, overridable |
| Factory Droid | npm package | pinned, overridable |
| OpenAI Codex | npm package | pinned, overridable |
Three properties they all share, each for a reason:
- The version is pinned and overridable. An unpinned global install makes your image's contents a function of the day you built it — the opposite of the digest pinning the rest of the set uses.
- They end as the unprivileged user. The install needs root; the agent never runs privileged.
- They verify the CLI during the build. A broken install fails your build instead of failing a run at three in the morning.
Codex looks like the odd one out and is not. It ships a native binary, so it is easy to assume it cannot come from npm. It can, and its npm package works the way Claude Code's does: a small launcher plus a per-platform package carrying the binary for your architecture. If your build host cannot reach npm, the codex build file's footer shows the three lines that install a binary you supply instead.
⛔ None of the four runs curl … | sh inside the build, and that is deliberate. A vendor's own
installer is the right command on a laptop and the wrong one in an image: a build that downloads an
unpinned artefact produces an image nobody can reproduce, which defeats the digest pinning
everything else here relies on.
⚠ Installing a CLI on a laptop or a bare host is a different question, and there the vendor's own installer is the right answer. Take it from the vendor's own page rather than from here — install URLs move, and a stale one in documentation is worse than no line at all.
What each CLI does behind a proxy
⚠ Read this before you put a CLI behind one, because two of the four ignore the variable most people reach for first. Every row names the version it was measured on — proxy behaviour is a property of a release, not of a brand.
| vendor · version measured | ALL_PROXY (SOCKS) | HTTP(S)_PROXY |
|---|---|---|
claude 2.1.266 · Linux | ⛔ IGNORED — goes direct | honoured, and fails closed |
codex 0.149.1 · Linux | honoured, fails closed — retries indefinitely | honoured, same |
qwen 0.22.2 · macOS, 0.22.3 · Linux | ⛔ IGNORED — goes direct | honoured, and names the dead proxy |
droid 0.213.0 · Linux | ignored — ⚠ a diagnostic reading only, see below | read and dialled — diagnostic only |
⛔ Two of the four ignore ALL_PROXY entirely. If your only control is a SOCKS proxy declared
that way, those two reach the network directly and your boundary is not doing what you think.
⚠ The droid row is the only diagnostic one. It measures what that vendor's own doctor subcommand does, which is a different question from what a governed run does. And ⛔ that subcommand exits successfully even with failing rows — read its rows, never its exit status.
⇒ The control that does not depend on a CLI's cooperation is egress control: a per-run proxy the run cannot route around, rather than an environment variable it may choose to honour.
What a container bounds, and what it does not
⚠ A container is not the network boundary. It bounds the toolchain and the filesystem a session sees. What a session may reach is egress control, and which confinement a container adds beyond the image is measured per machine rather than assumed — Sandboxing.
⚠ And one reason to reach for a container anyway: where a vendor's own sandbox collides with the engine's confinement, running that vendor in one of these images makes the container the single boundary. It is a recommendation for a stronger boundary, not a replacement for how that machine runs today — Sandboxing has the per-vendor detail.
Agent machines
The three forms an agent machine can take — a host, a container image, or a Linux virtual machine — and the per-language images the product ships as templates.
Android images: a JDK, not an SDK
The android class image carries a JDK and no Android SDK, why that will not change, and the permission class written against a default SDK install.