Docker Compose
Compose is the only deployment path this repository ships ready to use. There are two files:
docker-compose.yml (development, against a control-plane of your own) and
docker-compose.fleet.yml (an overlay for production).
Bringing it up
Section titled “Bringing it up”npm run dev:runner # = infisical run … -- docker compose up --builddocker compose up --build # the same thing, without InfisicalThe dev:runner script wraps Compose in Infisical because that’s the vault this team uses. If you
don’t use Infisical, the second command works identically: credentials just need to arrive through
one of the two paths below.
How credentials get into the container
Section titled “How credentials get into the container”Compose uses a list under environment:, not a map, precisely so it can mix fixed values
(NAME=value) with pass-through (bare NAME, no =). Pass-through means: take it from the host
shell, and if it doesn’t exist on the host, the key is omitted in the container, and it does not
become an empty string.
There are two paths, in order of preference:
# 1. vault → shell → pass-throughRUNNER_CONNECTORS=real DD_API_KEY=… GITHUB_TOKEN=… npm run dev:runner# 2. file (offline fallback)cp runner.secrets.env.example runner.secrets.env# fill it in, and the env_file picks it updocker compose up --buildWith neither, the runner stays in synthetic: fake data, no credentials.
The volumes, and what each one solves
Section titled “The volumes, and what each one solves”volumes: - ./dev-certs:/certs:ro - ${AWS_CONFIG_DIR:-${HOME}/.aws}:/root/.aws:rodev-certs/: the development PEMs minted by npm run dev:certs. The docker-entrypoint.sh
reads them from /certs and exports them as RUNNER_CERT_PEM, RUNNER_KEY_PEM, and RUNNER_CA_PEM,
because the runner reads PEM inline, not by path, which avoids the nightmare of multiline PEMs in
an env_file.
~/.aws: the required companion to RUNNER_AWS_CRED_MODE=chain. Without this volume the
container can’t see the cache aws sso login writes, and the credential chain fails without saying
so, the classic “works in my terminal, not in the container”. When the SSO token expires, run
aws sso login on the host: the container picks up the refreshed cache with nothing to restart.
Networking
Section titled “Networking”extra_hosts: - "host.docker.internal:host-gateway"On Linux this maps host.docker.internal to the host; on Docker Desktop it already resolves and the
line is a no-op. It’s what lets RUNNER_TUNNEL_URL=wss://host.docker.internal:8443 reach a
control-plane running on your machine.
restart: unless-stopped
Section titled “restart: unless-stopped”In development the runner picks itself back up. That’s intentional, and it pairs with the
1006 → exited 0 → restart cycle you see when the tunnel on the other side restarts: the exited 0
is the reconnect timer being unref’d, and the restart brings it back when the tunnel returns.
In production this behavior is inverted on purpose. See Production deployment.
Why the control-plane isn’t in this Compose file
Section titled “Why the control-plane isn’t in this Compose file”It consumes the @rootpilot/* packages through a file link to the sibling edge repository, which sits
outside any Docker build context. Containerizing it is blocked by a project interim. The development
arrangement is: control-plane on the host, runner in the container.