outrig image
outrig image groups commands for the container images that host an agent’s tools.
outrig image add scaffolds a repo-local image-config (a named Dockerfile + MCP-server
bundle); outrig image init scaffolds a standalone image project whose build output is a
reusable image, outrig image build builds and validates that project, and
outrig image inspect reads an image’s declared labels locally by default, or from a
registry with --remote. The rest of the group (image ls, image rm) is reserved for
later.
outrig image add
outrig image add scaffolds a new image-config. It writes a Dockerfile under
.agents/outrig/images/<name>/Dockerfile and appends matching [images.<name>] and
[images.<name>.mcp] blocks to your repo’s config.toml.
Run it any time you want to add an image-config – e.g., a planning config alongside the
<repo>-standard one outrig init creates by
default. init calls image add in a loop for the first (and any further) image-configs
you create during initial setup.
Bootstrapping a fresh repo
If you run outrig image add in a directory without an .agents/outrig/config.toml
(neither here nor in any parent), outrig prompts before scaffolding:
[outrig] no .agents/outrig/config.toml found in /path/to/repo or any parent.
? Configure outrig in this directory now? [Y/n]:
Answering y walks the same repo-config prompts that
outrig init uses (models, default agent,
preamble, image-config name, workspace), then continues with the image add flow for the
image-config you named. Given a <name>, it skips the image-config name prompt: <name> is
both the image-config image add scaffolds and the new config’s default-image.
Answering n exits with the same error a strict find would have produced – run
outrig init later when you’re ready.
Synopsis
outrig image add [<name>] [--force]
| Argument / flag | Default | Description |
|---|---|---|
<name> | prompted | Image-config name (becomes [images.<name>]). |
--force | off | Overwrite existing Dockerfile/config entries for this name. |
The name keys the [images.<name>] block, names the Dockerfile’s directory, and becomes the
repository of the image built from it, so it must be lowercase letters and digits, separated by
one ., one or two _, or a run of - (e.g. rust-dev; see
Reference -> Config). A <name> that isn’t is refused
before the first prompt, even with --force, and nothing is written; one typed at the prompt is
asked for again.
Run it
Every prompt shows the default in [default: ...]; press Enter to accept it. Type ? and
Enter at any text or yes/no prompt, or at a picker on piped stdin, for an explanation of what’s
being asked plus the available options; on a terminal, pickers print their options’
descriptions before they open and do not treat ? as help.
The default image-config name is <repo-folder>-standard (kebab-cased), so the example
below assumes a hello-outrig repo. A folder name with a letter outside ASCII, which no image
name can hold, suggests standard instead.
$ cd hello-outrig
$ outrig image add
? Image-config name [default: hello-outrig-standard]:
? Base image [default: debian:bookworm-slim]:
? Language toolchains [default: ]: rust, node
? MCP servers [default: fs]:
[outrig] wrote .agents/outrig/images/hello-outrig-standard/Dockerfile
[outrig] added [images.hello-outrig-standard] block to .agents/outrig/config.toml
[outrig] added [images.hello-outrig-standard.mcp] entries: fs
Next: try `outrig build` to verify the image builds, then `outrig run`.
The prompts are intentionally limited – the goal is a known-good starting point you can edit by hand, not an exhaustive Dockerfile generator.
Help at any prompt
? Language toolchains [default: ]: ?
Pick zero or more language toolchains to install in the image. The Dockerfile
template adds the corresponding install steps; you can edit the file afterwards.
rust rustup + stable toolchain (cargo, rustfmt, clippy).
node Node 24 LTS via NodeSource (apk on Alpine).
python CPython 3.12 with pip and venv.
go Go 1.27.
none Just the base image -- nothing extra installed.
See: https://tgockel.github.io/outrig/usage/image.html#known-toolchains
? Language toolchains [default: ]:
Known toolchains
The toolchain prompt offers curated options that cover the common cases:
| Choice | What gets installed |
|---|---|
rust | rustup + the stable toolchain, cargo, rustfmt, clippy. |
node | Node 24 LTS via NodeSource; apk on Alpine. |
python | CPython 3.12 with pip and venv. |
go | Go 1.27. |
none | Just the base image. |
You can pick more than one. The Dockerfile is a starting point – edit it freely afterwards.
Known MCP servers
fs– installs@modelcontextprotocol/server-filesystemfrom npm; default[mcp]entry:{ command = ["mcp-server-filesystem", "/workspace"] }.git– installsmcp-server-gitfrom PyPI; default[mcp]entry:{ command = ["mcp-server-git", "--repository", "/workspace"] }.
fs is the default. Add git for repo-aware tools, or wire up additional servers by
editing the [images.<name>.mcp] block directly – see
Concepts -> MCP Servers.
A shell MCP server is the usual next tool for coding image-configs; choose a package, install it
in the Dockerfile, and declare its command in [images.<name>.mcp].
TODO: Incomplete – the list of “known MCP servers” will grow as the ecosystem does. Anything not listed here you install in the Dockerfile by hand.
When templates do not fit
The prompt flow is intentionally small. If you need an image with a database, internal SDK,
unlisted MCP server, or other custom package set, use
outrig mcp self. It gives an MCP-capable AI tool the OutRig docs,
config schema, suggested tools, and advisory validators so it can propose a Dockerfile and
matching [images.<name>] block without being limited to the built-in template menu.
What gets written
.agents/outrig/images/hello-outrig-standard/Dockerfile:
FROM docker.io/library/debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl git build-essential \
&& rm -rf /var/lib/apt/lists/*
# rust toolchain
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --default-toolchain stable \
--profile minimal --component rustfmt,clippy \
&& chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME"
# node toolchain
RUN node_major=24 \
&& curl -fsSL "https://deb.nodesource.com/setup_$node_major.x" | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& case "$(node --version)" in \
"v$node_major".*) ;; \
*) echo "node toolchain: expected Node $node_major, found $(node --version)" >&2; exit 1 ;; \
esac \
&& npm --version
# MCP servers
RUN npm install -g @modelcontextprotocol/server-filesystem
WORKDIR /workspace
CMD ["sleep", "infinity"]
The Dockerfile is generic – no USER directive, no hard-coded UID, and nothing installed for
user management. outrig sets up a user matching your host UID/GID at run time, writing the
entries into the container from the host (see
Concepts -> Workspace).
That user is not root, so the rust toolchain installs under /usr/local, as the official rust
images do, with RUSTUP_HOME and CARGO_HOME set and left writable (see
Concepts -> Containers): cargo
keeps its registry cache in CARGO_HOME, and rustup installs any toolchain a project’s
rust-toolchain.toml pins into RUSTUP_HOME. To add to either – rustup component add,
cargo install – extend that RUN ahead of its chmod; a RUN of its own leaves what it adds
writable by root alone.
The node toolchain checks its own result. NodeSource’s setup script exits 0 even when one of
its steps failed, and apt-get install nodejs then takes the distro’s Node 18, which has no
npm; the build passes and the image is cached. So the RUN ends by requiring node to report
the major its setup_N.x URL names – set once, as node_major – and npm to run, and fails
the build otherwise.
Appended to .agents/outrig/config.toml:
[images.hello-outrig-standard]
dockerfile = ".agents/outrig/images/hello-outrig-standard/Dockerfile"
context = ".agents/outrig/images/hello-outrig-standard"
[images.hello-outrig-standard.mcp]
fs = { command = ["mcp-server-filesystem", "/workspace"] }
If your config spells images as an inline table, images = { base = { ... } }, image add
first rewrites it as a standard [images] table holding the same entries, since the new blocks
can’t nest inside an inline one. The table lands after your top-level keys. Comments above the
images line and at its end move to the [images] header; comments between the entries of a
multi-line inline table are not kept. An images that isn’t a table at all, such as
images = "legacy" or [[images]], is refused before the first prompt, even with --force, and
nothing is written.
Re-running
Without --force, outrig refuses if either the Dockerfile path or the config block already
exists for that name:
$ outrig image add hello-outrig-standard
error: .agents/outrig/images/hello-outrig-standard/Dockerfile
already exists; pass --force to overwrite.
With --force, the Dockerfile is replaced and the [images.<name>] block is rewritten in
place (preserving surrounding TOML).
Nothing is written until every prompt is answered, and a config that can’t be written leaves the Dockerfile unwritten too, new or replaced, so a retry isn’t refused over it.
outrig image init
outrig image init scaffolds a standalone image project: a self-contained directory whose
build output is a reusable container image. Unlike image add, it writes no repo config –
it creates three files and nothing else:
Dockerfile– a Debian-slim image with the filesystem MCP server, ending in the customaryCMD ["sleep", "infinity"]. It installs the tool binaries; it does not copy OutRig config.image.toml– the standalone image authoring config:[image].refplus a[mcp]table.outrig image buildvalidates this file and stamps the[mcp]table into the image’s OCI labels, which outrig reads back at session startup and merges with any repo[images.<name>.mcp].README.md– how to build the image and reference it from a repo.
The command is noninteractive – there are no prompts. The project name (used as [image].ref)
defaults to the target directory’s name.
Synopsis
outrig image init [<dir>] [--force]
| Argument / flag | Default | Description |
|---|---|---|
<dir> | current dir | Project directory; its name becomes the image ref. |
--force | off | Overwrite the generated files if they already exist. |
The directory name becomes the image’s ref, so it follows the same rule as an image add name:
lowercase letters and digits, separated by one ., one or two _, or a run of - (e.g.
rust-dev), though not 64 hex digits, which podman reads as an image ID. A target that resolves
to no usable name is rejected; . and the no-argument form use the current directory’s name.
What gets written
$ outrig image init rust-dev
[outrig] wrote rust-dev/Dockerfile
[outrig] wrote rust-dev/image.toml
[outrig] wrote rust-dev/README.md
[outrig] next: build this image with `outrig image build`
rust-dev/image.toml:
[image]
ref = "rust-dev"
[mcp]
fs = { command = ["mcp-server-filesystem", "/workspace"] }
[build] is omitted, so the build defaults to the sibling Dockerfile with context ..
Consuming the image from a repo
Once built, reference the image from a repo’s config.toml with image-name. Because the
MCP server declarations are stamped into image labels, the consuming block needs no
[images.<name>.mcp]:
[images.rust-dev]
image-name = "rust-dev"
That authoring split is the key difference from image add: repo-local blocks keep their
[images.<name>.mcp] entries in config.toml and stamp them into cache image labels when built;
standalone projects carry the declarations in image.toml so the image can be consumed elsewhere
without repo-local MCP config. See
Concepts -> Containers for the
image-name shape.
A ${VAR} in a [mcp] entry’s env is read from the machine that runs the image, not the one
that built it, and only if that machine’s global config lists the variable in
[repo] env-allow: a session refuses an image whose label
references an unlisted one, naming the server, the key, the variable, and the image.
outrig image build does not hold the image.toml it is pointed at – you chose that directory,
and the live test runs its servers with their references resolved.
Re-running
Without --force, init refuses if any of the three files already exists, naming the
conflicts:
$ outrig image init rust-dev
error: rust-dev/Dockerfile, rust-dev/image.toml, rust-dev/README.md
already present; pass --force to overwrite.
--force regenerates the three files and leaves anything else in the directory untouched.
outrig image build
outrig image build builds a standalone image project (the output of outrig image init) and
validates that the result is a working OutRig toolset image. It is the standalone-project
counterpart to outrig build, which builds
repo-local image-configs.
It reads the project’s image.toml, serializes it into OCI labels, builds the declared
Dockerfile with buildah, and tags the result with the [image].ref from image.toml –
stamping those labels onto the image. Then it always validates the built image, and – unless
--no-test – live-tests every MCP server the image declares.
Synopsis
outrig image build [<dir>] [--tag <ref>] [--no-test] [--no-cache]
| Argument / flag | Default | Description |
|---|---|---|
<dir> | current dir | Project directory holding image.toml. |
--tag <ref> | [image].ref | Build tag override; does not edit image.toml. |
--no-test | off | Skip the live MCP test; still validates image.toml. |
--no-cache | off | Force a clean build; passed through to buildah. |
What it does
$ outrig image build rust-dev
[outrig] building image rust-dev
[outrig] dockerfile: Dockerfile
[outrig] context: .
... buildah build output ...
[outrig] image ready
[outrig] image config validated (1 mcp server)
[outrig] mcp fs: initialized (12 tools)
[outrig] image ok
After the build, outrig reads the stamped org.outrig.mcp label back off the built image and
validates it (every server name well-formed, every command non-empty, every call-timeout-secs
between 1 and 3600). By default it then starts
the image, and for each declared MCP server initializes it and calls tools/list, reporting the
tool count per server. The image is built locally; nothing is pushed.
Ctrl-C (or SIGTERM, or SIGHUP) once the image is built stops and removes that validation
container before outrig exits, with 130, 143, or 129. A server that never answers its
initialize no longer needs it: the test gives up on it after 120 seconds, and on a
tools/list after another 120. During the build itself, Ctrl-C reaches buildah
directly, which removes the working containers it created; see
Cancelling a build.
Failure modes
The command exits nonzero – printing error: ... – in these cases:
image.tomlmissing, malformed, or missing required fields (no[image].ref, an empty or invalid[mcp]table, a partial[build]): caught before the build runs.- The stamped
org.outrig.mcplabel is missing or malformed: read back off the built image and validated after the build, even with--no-test. - A declared MCP server cannot initialize or return
tools/list, including one that does not answer either within 120 seconds: caught during the live test.--no-testskips this check (the per-server stderr is captured in the error message).
--no-test
--no-test skips only the live MCP server test. The stamped org.outrig.mcp label is still
read back from the built image and validated, so a missing or malformed config still fails. Use
it to verify a build quickly, or in environments where starting the servers is not wanted.
--tag and --no-cache
--tag <ref> tags the build output as <ref> for that invocation instead of the [image].ref
in image.toml; the file is never rewritten. Short refs are passed to buildah verbatim, so both
rust-dev and rust-dev:0.1.0 work.
Unlike repo-local outrig build – which tags <image-config-name>:<content-hash> and skips
the build on a cache hit – a standalone build tags a stable, caller-named ref with no content
hash, so there is no project-level cache to skip: --no-cache only forwards --no-cache to
buildah to force a clean build.
The build itself runs into a temporary outrig-tmp-* tag and is renamed to <ref> once it
succeeds, so a cancelled or failed build cannot leave a half-built image under the name you
asked for – which may already belong to something else. The rename adds a name to an image
that already exists; no layer is copied. A cancelled build is asked to stop rather than killed,
so buildah removes the per-stage working containers it made; see
Concepts -> Containers.
outrig image inspect
outrig image inspect prints the OutRig config labels declared by an image. It is read-only:
local mode checks the local image store and reads labels with podman image inspect; remote mode
uses skopeo inspect against a registry. Neither mode pulls image layers, creates a container, or
starts an MCP server. Live initialization and tools/list checks belong to
outrig image build.
Synopsis
outrig image inspect [--remote] <ref>
| Argument | Description |
|---|---|
<ref> | Image ref to inspect. Never pulled. |
--remote | Read registry metadata with skopeo inspect. |
Output
The command writes only the inspect result to stdout. Diagnostics and errors go to stderr.
$ outrig image inspect rust-dev
image: rust-dev
description: Rust tooling
version: 0.1.0
tags: ["rust","build"]
mcp:
fs:
command: ["mcp-server-filesystem","/workspace"]
db:
command: ["db-mcp"]
env:
TOKEN: "${DB_TOKEN}"
description, version, and tags appear only when the image carries those labels. If the
image has no org.outrig.mcp label, inspect still prints the image ref and any metadata labels it
finds, then omits the mcp: section.
Repo-local images built by outrig build, outrig run, or outrig mcp are stamped with an
org.outrig.mcp label on cache misses. For those <image-config-name>:<hash> refs, inspect shows
the merged MCP declarations from inherited/Dockerfile labels plus [images.<name>.mcp], without
starting a container. Standalone images may additionally show description, version, and
tags.
Use --remote when the ref is in a registry and you want the labels without pulling it:
outrig image inspect --remote ghcr.io/acme/rust-dev:latest
Remote inspection requires skopeo on PATH. Authentication uses the normal container
credentials that skopeo reads, such as credentials created by skopeo login, podman login,
buildah login, or docker login.
Failure modes
The command exits nonzero – printing error: ... – in these cases:
- The image is not present locally: inspect reports a local-only not-found error and does not attempt a pull.
--remoteis used butskopeois not installed: inspect reports the missing runtime dependency.- The registry request fails:
skopeoerrors, including missing credentials or denied access, are reported with the command and stderr tail. - The remote ref uses an unsupported transport: use a plain registry ref or
docker://<ref>. - A declared OutRig label is malformed: invalid
org.outrig.tagsororg.outrig.mcpvalues fail before anything is started.
See also
- outrig init – runs
image addin a loop as the last step of initial setup. - Concepts -> Containers – Dockerfile conventions and named image-configs.
- Concepts -> MCP Servers – the MCP servers
image addscaffolds. - AI-assisted design – design a custom image-config with
outrig mcp self. - Reference -> Config – the
[images.<name>]schema.