Container images
Outstaff publishes two images to ghcr.io/outstaff-ai, for linux/amd64 and linux/arm64. There is one Dockerfile, images/Dockerfile, with one target per image. Both targets share the builder stage and the pinned base. The office image contains only Node, tini, CA certificates, git (for OUTSTAFF_TEAM_REPO) and the office bundle: no mise, gh or Claude Code. Agents use ost pr for pull requests, so no GitHub CLI is shipped. Each release tag vX.Y.Z publishes :X.Y.Z of both; :latest moves only for releases without a prerelease part. Pin the version in your own setup.
| Image | Run as | Contains |
|---|---|---|
ghcr.io/outstaff-ai/office |
outstaff-office |
Node, tini, CA certificates, git (to read a team config from a repository) and the office bundle. Health on port 8081. |
ghcr.io/outstaff-ai/agent |
ost-agent |
The agent, the ost CLI, the stock catalog, git, mise and the Claude Code binary. Runtimes: claude-agent-sdk. |
Runtimes an image carries
Section titled “Runtimes an image carries”An agent runs on the runtime adapter its person names (people[].runtime, else integrations.runtime), and that adapter has to be in the image: it is code, not configuration. The agent image carries claude-agent-sdk. Each agent image lists its runtimes in the label work.outstaff.runtimes (space-separated), and ost-agent --runtimes prints the same list, one per line:
docker image inspect -f '{{ index .Config.Labels "work.outstaff.runtimes" }}' ghcr.io/outstaff-ai/agent:1.2.3docker run --rm ghcr.io/outstaff-ai/agent:1.2.3 ost-agent --runtimesAn agent whose person names a runtime the image does not carry stops at start with an error that names the person and lists what the image carries. See Runtimes.
Tools come from the toolsets
Section titled “Tools come from the toolsets”The agent image is small on purpose. The language toolchains and CLIs a role needs (Node package managers, Python, Go, kubectl and so on) are declared in the toolsets and team.yaml, and the agent installs them with mise when it starts (#156). mise keeps its installs in MISE_DATA_DIR and MISE_CACHE_DIR below the agent’s home, so the home volume keeps them between restarts and a restart does not download them again.
If you want instant starts, or a machine without outbound access to the tool hosts, build an image FROM the agent image with the tools already installed (see below). The agent finds them on PATH and has nothing left to install.
The container contract
Section titled “The container contract”- Non-root. All images run as user
outstaff(uid 10001) and use tini as init. - Read-only root filesystem. Run with
--read-only(orreadOnlyRootFilesystem: true) and give the containers writable places:- the agent:
/home/outstaff(itsHOME: worktrees, sessions, journal, memory) and/tmp; - the office:
/var/lib/outstaff(the SQLite database, unless you use Postgres throughOUTSTAFF_STORE_URL) and/tmp.
- the agent:
- No secrets inside. Secrets come from files under
/run/secrets/outstaff, or from the environment as a fallback (0008). - Health. The office image has a
HEALTHCHECKthat calls/healthzonOUTSTAFF_HEALTH_PORT(default 8081). Use/readyzfor readiness. The agent has no port; it exits when the office rejects it, so let your platform restart it. - Versions.
ost-agent --version,ost --versionandoutstaff-office --versionprint the image’s release version, which the image sets asOUTSTAFF_VERSIONfrom the build argument of the same name (the release workflow passes the tag’s version). The office reports it on its link and the agents in theirhello. Built without the argument, they print the package version. - Runtimes.
ost-agent --runtimesprints the runtime adapters the agent image carries, the same list as itswork.outstaff.runtimeslabel.
Extending an image
Section titled “Extending an image”The catalog is baked in (0005), so a team that does not change it uses the images as they are. A team that ejects roles or skills, or needs more tools, builds its own image:
FROM ghcr.io/outstaff-ai/agent:1.2.3
USER rootRUN apt-get update \ && apt-get install -y --no-install-recommends python3 make \ && rm -rf /var/lib/apt/lists/*USER outstaff
# The team's ejected catalog, validated and rendered at build time.COPY --chown=outstaff:outstaff .outstaff /home/outstaff/.outstaffRUN ost buildost build validates and renders the ejected content so a typo fails the build, not a running agent. It arrives with the eject commands; until then the COPY is all an ejected team needs. Keep these rules in your own image:
- switch back to
USER outstaffat the end; - do not put secrets in
ARG,ENVor a layer; use BuildKit--secretwhen a build step needs one; - write only below
/home/outstaffand/tmpat run time, so the image stays usable with a read-only root; - pin what you add the same way (see below) and rebuild when a new Outstaff release comes out.
Pinning policy
Section titled “Pinning policy”Everything in the images is pinned, so a tag always means the same bits:
- Base image:
node:24.22.1-slim(Debian 12), by version and by the digest of its multi-arch index, matching the Node version the repository itself requires. - npm packages: exact versions in every
package.json, resolved throughpnpm-lock.yaml(--frozen-lockfile). Only production dependencies reach the images: the builder stage runspnpm deploy --prodper package. - Claude Code binary: the Claude Agent SDK ships it as a platform package (
@anthropic-ai/claude-agent-sdk-linux-x64or-linux-arm64, glibc). The SDK version is an exact pin and the lockfile holds the integrity hash of each platform package, so a release pins the Claude Code version too (0009). The agent build runs the binary with--versionand fails if it is missing. - CLI tools (
mise): exact versions, downloaded from the vendor and verified against a sha256 per architecture inimages/Dockerfile. A mismatch fails the build. - Debian packages (git, tini, ca-certificates): taken from the pinned base image’s Debian release at build time. They are not version-pinned individually; rebuild images for security updates.
To bump a tool, change the version and both checksums in the build arguments of images/Dockerfile together, taking the checksums from the vendor’s published checksum file.
Building and testing the images
Section titled “Building and testing the images”From the repository root:
docker build -f images/Dockerfile --target agent -t outstaff/agent:dev .docker build -f images/Dockerfile --target office -t outstaff/office:dev .images/smoke.sh outstaff devimages/smoke.sh starts each image with a read-only root, checks the version commands (including mise --version; with SMOKE_VERSION set, --version must print exactly that, the OUTSTAFF_VERSION build argument the images were built with), checks that the agent’s work.outstaff.runtimes label matches ost-agent --runtimes, installs pnpm and yarn from the code toolset with mise and runs pnpm --version and yarn --version in an unrelated directory (this needs egress to the tool sources), and starts the office on SQLite with a minimal team file until its health check passes. The Images workflow does the same on every pull request and in the merge queue for linux/amd64, and for linux/arm64 on a native arm64 runner every night and on v* tags before it publishes.