Skip to main content
The runner image is a container image that runs the runner. You start it with one docker run, on a Linux machine with Docker Engine or on a Mac with Docker Desktop. You install no CLI and you write no config file. Docker’s restart policy keeps the runner running. Builds run as containers beside the runner’s container, on the host’s Docker. The runner image cannot run microVM builds. For those, see Set up your first host.

Before you start

You need:
  • A Linux machine with Docker Engine 26 or later, or a Mac with Docker Desktop. The engine behind Docker Desktop must also be version 26 or later. Check it with docker version --format '{{.Server.Version}}'. The runner image refuses to start with an older engine. On a Mac, read Docker Desktop first.
  • A runner key. It is an API key with the Runner preset. Create it as Give the runner its key shows.
  • The factory’s id. It is the Factory ID on Settings → General.
  • A Claude credential. Create a token with claude setup-token on a machine where Claude Code is installed, or use an Anthropic API key.

Start the runner

The runner holds the host’s Docker socket, so it controls the host’s Docker daemon. Builds never get the socket. See The Docker socket in a runner container.
  1. Put the settings in a file, so they stay out of your shell history. The file holds the runner key, so make it readable only by your account.
    runner-image.env
  2. Start the container. Use --env-file, not -e: a value in -e stays in your shell history.
Anyone who can run docker inspect on the host can read the runner key and the Claude credential. That person already controls the Docker daemon.

The variables

The file must set these variables: These variables are optional: The container refuses to start, and names the problem, when a required variable is missing or a value is not valid.

The image tags

Each release has these tags:
  • <version>, such as 0.31.0. It always names that exact release.
  • <major>.<minor>, such as 0.31. It names the newest patch release of that minor version.
  • latest. It names the same version as the latest tag of @outerlayer/cli on npm.
A tag does not move a running container. Docker reads a tag only when you pull or run, so a container keeps its version until you recreate it.

Check that it runs

The factory’s Settings → Runners page lists the runner under the name in OUTERLAYER_RUNNER_NAME.

What the volume holds

The volume outerlayer-runner holds everything the runner keeps: its config file, its host key and the state of its builds.
Keep the same volume when you recreate the container. A new volume makes a new host key, and the factory then refuses the runner key. To fix that, see Moving a runner to a new host.
The first start writes the config file from the variables. A later start uses the config file in the volume, and these rules apply:
  • The factory, key and name cannot change. The runner refuses to start when OUTERLAYER_URL, OUTERLAYER_APP_ID, OUTERLAYER_API_KEY or OUTERLAYER_RUNNER_NAME disagrees with the volume. To move a runner to another factory, key or name, use a new volume.
  • OUTERLAYER_BUILD_MEMORY and OUTERLAYER_CONCURRENCY replace the value in the volume at each start. A variable you remove leaves its last value there. See Stop setting the build memory.
  • The Claude credential is never written to the volume. To replace it, change it in the env file and recreate the container with the same volume.
Other settings, such as queues and the time limit, are in the config file in the volume, /root/.outerlayer/config.json. Edit it with docker exec. The runner reads the file again when it changes. See The config file.

Stop setting the build memory

A line you remove from the env file does not remove its value from the volume. The value stays in runner.build.memory, so the runner does not fit the build memory to the machine again. To let the runner fit the build memory again:
  1. Remove the OUTERLAYER_BUILD_MEMORY line from the env file.
  2. Delete runner.build.memory from the config file in the volume:
  3. Recreate the container with the same volume.
OUTERLAYER_CONCURRENCY works the same way. To go back to one build at a time, delete runner.concurrency with the same command.

Docker Desktop

On a Mac, the runner image runs in Docker Desktop’s Linux VM, on Apple silicon and on Intel. Builds run as containers in that VM and record container. What a build there can reach is in Security and limits.
Docker Desktop needs a paid Docker subscription for some companies. Read Docker’s terms before you choose the runner image over Lima. Lima is free. See Run the runner on a Mac.
Docker Desktop on Windows passes the same start check. OuterLayer has not tested it end to end. See Run the runner on Windows.

Size the VM

By default each build is limited to 8g of memory, 4 CPUs and 40g of disk. Docker Desktop’s default VM can be smaller. A small Linux machine gets the same fit.
  • Memory. The runner claims work only when the VM’s memory is at least runner.concurrency times runner.build.memory, plus 2 GiB. With the defaults, that is 10 GiB. When nothing sets the build memory and 8g does not fit, the runner lowers it to fit, down to 4g, and logs the value. A VM with 8 GB gives each build 5g. A VM too small for 4g claims no work.
  • CPUs. A VM with fewer than 4 CPUs gives each build all of its CPUs.
  • Disk. The runner claims work only when the VM’s disk has runner.housekeeping.minFreeDisk free, 40 GB by default. The VM’s disk also holds every other image and volume on the Mac.
To give the VM more memory, CPUs and disk, open Docker Desktop’s Settings → Resources. Then restart the container. To fit a small VM instead, lower the limits:
  • Set OUTERLAYER_BUILD_MEMORY or OUTERLAYER_CONCURRENCY in the env file, and recreate the container.
  • Set runner.build.cpus, runner.build.disk and runner.housekeeping.minFreeDisk in the config file in the volume.
  • Set runner.housekeeping.savedStateMax lower. The runner keeps up to that much, 20 GiB by default, in saved states from setup scripts.

Start the runner after a reboot

Two settings bring the runner back with no one starting it:
  • In Docker Desktop’s settings, turn on Start Docker Desktop when you sign in.
  • Start the runner container with --restart unless-stopped, as the command above does.
Docker Desktop stops every container when it restarts, builds included. A build that had not finished ends as interrupted, so the item can be built again.

Operate the runner

Drain before maintenance

To let the running builds finish before you stop the runner, drain it:
The runner then takes no more work and keeps managing its builds. The command returns at once, and the container stays up. To see what is left, run:
Run docker stop outerlayer-runner when no build is left.

Stop the runner

docker stop outerlayer-runner stops the runner and leaves every build running. The container that starts next, with the same volume, takes the builds over. Start the next container soon. A build keeps its claim only while a runner renews it, and the claim expires after 15 minutes. A build’s network connections also fail while no runner runs.

Update the runner

A runner in the image does not update itself, whatever the factory’s Runner updates setting says. An update is a new image. The runner’s log, outerlayer runner status and outerlayer doctor name the tag to pull. To update, follow The runner image on the update page. It keeps the container’s mounts, so the new container is the same runner. Builds survive the change. Update before the support window ends, 30 days after OuterLayer names a newer version. A runner outside the window claims nothing.

Pin the image by digest

The latest and <major>.<minor> tags move with each release. A digest does not move. Pin the image when a host must run one tested release until you change it.
  1. Find the digest of the version you tested:
  2. Name the image by its digest in docker run: