Docker Compose
Compose is the most direct way to run the Agent on a single host — a dedicated VM, for example. For orchestrators, see Kubernetes or the production setup.
A minimal compose.yml
Section titled “A minimal compose.yml”services: rootpilot-agent: image: <address>/rootpilot-agent@sha256:<digest> restart: unless-stopped stop_grace_period: 150s volumes: - rootpilot-identity:/identity environment: RUNNER_TUNNEL_URL: wss://tunnel.rootpilot.sh:8443 RUNNER_ENROLL_URL: https://app.rootpilot.sh RUNNER_ATTESTATION_MODE: bootstrap-token RUNNER_BOOTSTRAP_TOKEN: ${RUNNER_BOOTSTRAP_TOKEN} RUNNER_IDENTITY_DIR: /identity RUNNER_INSTANCE_ID: agent-prod-1 RUNNER_CONNECTORS: real RUNNER_SECRET_STORE_MODE: aws NODE_ENV: production
volumes: rootpilot-identity:stop_grace_period larger than RUNNER_DRAIN_TIMEOUT_MS (2 minutes by default), or SIGKILL
lands mid-drain.
The identity needs a writable volume
Section titled “The identity needs a writable volume”It is what makes docker compose pull && up -d not cost a new bootstrap token. Without it, the
identity dies with the container and the previous token is already spent — it is single-use.
Prefer a named volume, as in the example above. The image already ships /identity owned by uid
1000 (it runs as non-root), and a named volume inherits that ownership when it is created — so
it works without you doing anything.
A bind mount (./identity:/identity) inherits nothing: there, the ownership is the host directory’s,
and it has to belong to uid 1000. Never mount it with :ro.
If the directory is not writable, boot does not fail — the Agent warns and carries on, and you only find out on the next deploy:
could not persist identity — next boot will re-enroll from scratchThe certificates, if you use a provisioned certificate
Section titled “The certificates, if you use a provisioned certificate”volumes: - ./certs:/certs:roThe directory must hold runner.crt, runner.key, and ca.pem. The entrypoint reads them and
exports them as RUNNER_CERT_PEM, RUNNER_KEY_PEM, and RUNNER_CA_PEM — the Agent reads PEM
inline, not as a path, and mounting the directory avoids the nightmare of multi-line PEMs inside
an env_file.
That path ignores enrollment and persisted identity: the two blocks above stop applying, and the certificate does not renew. See Identity and enrollment.
If the Agent runs on EC2 and uses the credential chain
Section titled “If the Agent runs on EC2 and uses the credential chain”With RUNNER_AWS_CRED_MODE=chain, the SDK’s default chain resolves through the instance profile with
no extra volume. If you instead use a local file-based profile, mount it:
volumes: - ${HOME}/.aws:/home/node/.aws:roWithout that volume the container cannot see the profile, and the chain fails without saying so — the classic “works in my terminal, not in the container”.