Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 / flagDefaultDescription
<name>promptedImage-config name (becomes [images.<name>]).
--forceoffOverwrite 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:

ChoiceWhat gets installed
rustrustup + the stable toolchain, cargo, rustfmt, clippy.
nodeNode 24 LTS via NodeSource; apk on Alpine.
pythonCPython 3.12 with pip and venv.
goGo 1.27.
noneJust 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-filesystem from npm; default [mcp] entry: { command = ["mcp-server-filesystem", "/workspace"] }.
  • git – installs mcp-server-git from 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 customary CMD ["sleep", "infinity"]. It installs the tool binaries; it does not copy OutRig config.
  • image.toml – the standalone image authoring config: [image].ref plus a [mcp] table. outrig image build validates 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 / flagDefaultDescription
<dir>current dirProject directory; its name becomes the image ref.
--forceoffOverwrite 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 / flagDefaultDescription
<dir>current dirProject directory holding image.toml.
--tag <ref>[image].refBuild tag override; does not edit image.toml.
--no-testoffSkip the live MCP test; still validates image.toml.
--no-cacheoffForce 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.toml missing, 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.mcp label 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-test skips 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>
ArgumentDescription
<ref>Image ref to inspect. Never pulled.
--remoteRead 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.
  • --remote is used but skopeo is not installed: inspect reports the missing runtime dependency.
  • The registry request fails: skopeo errors, 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.tags or org.outrig.mcp values fail before anything is started.

See also