Skip to content

Production deployment

Terminal window
npm run fleet:up # anonymous fleet, synthetic connectors
RUNNER_TENANT=<slug> npm run fleet:up # a tenant's fleet, real connectors

fleet:up applies docker-compose.fleet.yml as an overlay on the development Compose file. Every difference between the two exists for a specific reason.

wss://tunnel.rootpilot.sh:8443 is the runners’ tunnel port. Other control-plane ports and hostnames will not accept a runner.

The overlay redefines volumes with !override, and not for style: Compose merges volume lists, so redefining without !override would leave dev-certs/ mounted. The entrypoint would then set RUNNER_CERT_PEM from the development CA, the runner would take the provisioned-certificate shortcut instead of enrolling, and mTLS would fail against the production CA with an error that says none of this.

~/.aws stays mounted: it’s what backs RUNNER_AWS_CRED_MODE=chain.

Connecting to production is a deliberate act. A runner that resurrects itself after a mistaken docker compose down becomes a ghost presence in a real tenant’s Fleet view.

It does not pick the tenant. The bootstrap token does that: it’s minted per tenant, and enrollment returns a certificate with that tenant’s SPIFFE id inside. Getting this slug wrong will not connect you to the wrong organization.

It does two things: it picks the credentials folder in the vault (/rootpilot/edge/<slug>) and it turns on RUNNER_CONNECTORS=real. The second is deliberate: bringing up “organization X’s fleet” with synthetic data is a no-op that looks like it worked, and that is the most expensive class of failure this product has.

For the actual BYOC path, what changes is where the container runs and how it proves identity:

  • Identity without a human: swap bootstrap-token for a cloud identity: RUNNER_ATTESTATION_MODE=aws-sts on EC2/EKS, gke-oidc on GKE. See Attestation.
  • Secrets from your vault: RUNNER_SECRET_STORE_MODE=aws or vault, rather than env. See Secret stores.
  • Cloud credentials from the default chain: RUNNER_AWS_CRED_MODE=chain (IRSA, instance profile) rather than static keys. See Cloud credentials.

The enrollment certificate is valid for 1 hour. RUNNER_CERT_RENEWAL=on is the default, and it’s what keeps a cattle fleet alive without human intervention. Without it, every runner has a one-hour shelf life and only a human minting a bootstrap token brings it back.

Renewal requires a control-plane with the verifier. Against an older control-plane, renewal fails, the runner degrades to the old behavior (expires in 1h), and logs cert renewal failed rather than dying quietly.

If the certificate goes past the point of recovery, the runner does not come back on its own, because the renewal attestation proves possession with the certificate that expired. The process then exits, so that the supervisor brings up a fresh runner that enrolls from scratch.

This is deliberate: without that exit, the process would become a zombie: up, serving nothing, and invisible to any probe, since the runner is an outbound client with no health endpoint.

Make sure your supervisor (Compose restart, a ReplicaSet, an ECS service) is configured to bring up the replacement.