builtin:container:
MicroVM builds
With both hooks set tobuiltin:vm, each build runs in a Firecracker
microVM with its own kernel. The host must be x86_64 Linux, with Docker
Engine 26 or later and Docker Buildx 0.18 or later. The runner’s account
must be able to open /dev/kvm. runner init names
builtin:vm on such a host; the full rule is in
outerlayer runner init.
outerlayer doctor fails builtin:vm hooks on any other host, and says
why.
The microVM is made from the recipe’s image, with the same tunnel, tokens
and limits a container build has. The tunnel, the destinations, the branch
rule and the failure reasons below apply to it too. It has no network device: its connections leave over vsock to the
runner’s tunnel. A recipe that adds the docker-in-docker feature gets its
own Docker daemon inside the microVM. That is how a build that needs Docker
runs.
The first microVM build downloads a pinned Firecracker release, a guest
kernel and a small guest helper from github.com. The runner checks each
file against a sha256 pinned in the CLI, and fails the build at provision
if one differs.
What a build gets
The runner builds the repository’s image, asouterlayer runner image
does, and starts one container from it. The container has no network but
loopback, a read-only root filesystem, and one volume for the clone. Your
home directory, other builds and the Docker socket are out of reach.
Commands run as the recipe’s remoteUser, else as the image’s own user.
When that is root, or neither names one, they run as ol-agent. A
lifecycle command that exits non-zero fails the build at provision, with
a reason naming the command.
The build block
variables is the only way a host variable enters a build. Claude’s
credential reaches it only as a placeholder. Repository tokens (GH_TOKEN,
GITHUB_TOKEN, GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN) are never
passed; the runner logs each name it skipped. A name the runner sets itself,
such as HOME, PATH or a proxy variable, is refused.
To size the host for these limits, see
Memory limits.
The gateway and Claude
Every build the runner provisions with a built-in hook (builtin:vm,
builtin:container or builtin:process) gets two built-in
destinations. The build holds neither the item
key nor Claude’s credential.
The runner reads
CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token), or
else ANTHROPIC_API_KEY, from its own environment; restart it after a change.
A terminal claude login is not forwarded. With neither set, each build fails
at provision as agent_credential_missing. See
Give the runner Claude’s credential.
A build your own hooks run gets no built-ins. It holds the item key as
OUTERLAYER_API_KEY; see The build’s variables.
The tunnel
The container’s only way out is a tunnel the runner serves for that build. The runner sets the standard proxy variables to it,http://127.0.0.1:3128.
A tool that ignores them cannot connect; set pip’s --proxy or Maven’s
settings.xml to that address in the recipe.
A build cannot reach the host’s own addresses, private or loopback addresses,
link-local addresses including the cloud metadata address 169.254.169.254,
or Tailscale addresses. A refusal is a 403 naming the range. Listing such a
host in allowHosts does not change that. To reach a private service, make it
a destination.
Limiting the hosts a build reaches
*.githubusercontent.com, which matches names under the domain but not the
domain itself. An empty list refuses every public host. An unlisted name gets
a 403 with the range not-allowed. The list does not limit the gateway,
Claude, your destinations or the git remote.
To find the hosts your builds need, run a few without the list, then run
outerlayer doctor on the host. While the list is unset, its “Build hosts”
check warns and lists every host past builds reached, ready to paste into
allowHosts. Review it first: a host a build should never have reached is
in it too. Each build’s own list is hosts in its environment (see
What a build records).
The git remote and the build’s tokens
A container build holds no credential that can write to its repository. Git andgh reach GitHub through the runner, which adds short-lived read and push
tokens the gateway issues
(GitHub App permissions and build tokens).
gh can read, such as gh pr view, but not write.
The branch rule. A push may update only the claim’s branch. On an
outerlayer/… branch any update goes through, force-pushes included;
elsewhere only creating the branch and fast-forwards. A refused push updates
no ref, the runner logs push refused: <refs>, and git prints:
A governed repository’s context
When a control-plane repository governs the item’s repository, a container build starts with its context (AGENTS.md, .claude/, .outerlayer/) in
the checkout. The runner fetches it on the host; the build never commits it.
The host’s git login must read the control plane. outerlayer doctor
checks this as Control plane access.
Failure reasons
A read token that fails for a temporary reason, such as
github_unavailable,
releases the claim with no outcome; the runner retries on its next poll. A
refused push token fails the push, and the build fails at its next health
check.
Stopping a build
Stopping a build stops its container. Cleanup removes the container and volume; a runner that crashed finishes it on restart. If the runner logscleanup failed, its next housekeeping
pass removes what the build left, within the hour. To free the space
sooner, remove what it names with docker rm --force and
docker volume rm.
What a build records
outerlayer work status --json shows each build’s environment under
claims: the isolation, image, hosts reached and refused, variable and
destination names, and CLI versions. It never holds a secret.