Shipfox
Reference

Runner Provisioner Configuration Reference

Every Shipfox runner provisioner environment variable with its default, plus the runner template file schema: vars, defaults, matrix families, labels, image, cpu, memory, cost, and max_concurrency.

This page is the canonical reference for configuring the Docker runner provisioner (@shipfox/provisioner-docker). For how a provisioner works, see Runner Provisioners; for starting one, see Run a provisioner.

Required variables

VariablePurpose
SHIPFOX_API_URLBase URL of the Shipfox API the provisioner connects to.
SHIPFOX_PROVISIONER_TOKENLong-lived token that authenticates control-plane calls. Create it for the provisioner scope you intend to run and keep it secret. The value is shown once and can be revoked.
SHIPFOX_PROVISIONER_TEMPLATES_FILEPath to the YAML file describing the runner templates this provisioner can start.

Optional variables

VariableDefaultPurpose
SHIPFOX_RUNNER_API_URLSHIPFOX_API_URLBase URL injected into runner containers as their SHIPFOX_API_URL. Set it when containers reach the API through a different address than the provisioner uses.
SHIPFOX_RUNNER_POLL_MAX_DURATION_MS300000Injected into each runner as SHIPFOX_POLL_MAX_DURATION_MS: how long a started runner polls for a job before exiting. Use 0 to poll forever.
SHIPFOX_PROVISIONER_DOCKER_HOSTlocal socketDocker daemon host. Set a Docker host URL when the daemon is remote.
SHIPFOX_PROVISIONER_DOCKER_NETWORKunsetDocker network attached to runner containers, for reaching the API through a Compose or bridge network.
SHIPFOX_PROVISIONER_DOCKER_EXTRA_HOSTSunsetComma-separated host mappings added to runner containers, such as host.docker.internal:host-gateway.
SHIPFOX_PROVISIONER_DOCKER_LOG_DRIVERDocker daemon defaultLogging driver for runner containers. Set this to override the daemon default for this provisioner.
SHIPFOX_PROVISIONER_DOCKER_LOG_OPTIONSunsetJSON object of string-valued logging-driver options. Requires SHIPFOX_PROVISIONER_DOCKER_LOG_DRIVER; option values are never written to provisioner logs.
SHIPFOX_PROVISIONER_POLL_WAIT_SECONDS30How long each demand poll waits for work before returning. 0 makes the poll non-blocking.
SHIPFOX_PROVISIONER_POLL_INTERVAL_MS1000Wait between demand polls. Backs off toward the max after errors.
SHIPFOX_PROVISIONER_POLL_MAX_INTERVAL_MS5000Largest poll backoff interval after repeated errors.
SHIPFOX_PROVISIONER_CONVERGE_INTERVAL_MS1000Provider observation and reconciliation cadence. Backs off on errors up to the larger of 5000 ms and the configured cadence.
SHIPFOX_PROVISIONER_MAX_RESERVATIONS250Most reservations requested in one poll. Also bounded by free template capacity and the API's cap of 1000.
SHIPFOX_PROVISIONER_RUNNER_INSTANCE_BATCH_SIZE250Runner instances created per request (1 to 1000). Must not exceed the API's own batch limit.
SHIPFOX_PROVISIONER_REGISTRATION_DEADLINE_MS120000How long a created container may stay unstarted before the provisioner reaps it as stale.
SHIPFOX_PROVISIONER_DOCKER_FAILED_CONTAINER_RETENTION_MS3600000How long failed runner containers remain stopped for forensic inspection. Set to 0 to disable retention.
SHIPFOX_PROVISIONER_DOCKER_MAX_RETAINED_FAILED_CONTAINERS20Maximum number of failed runner containers retained per provisioner. Set to 0 to disable retention.
SHIPFOX_RUNNER_MAX_LIFETIME_SECONDS3600Hard maximum lifetime injected into each runner. Set it above the longest permitted job so a runner terminates if its provisioner is unavailable.

Docker logging and failed-container forensics

Provisioner logs and runner-container output are separate streams. The provisioner never copies runner stdout or stderr into its own logs. Use LOG_LEVEL, LOG_PRETTY, LOG_STDOUT, LOG_STDOUT_LEVEL, LOG_FILE, and LOG_FILE_LEVEL for provisioner-process logs. LOG_FILE writes a plain file without built-in rotation, so configure external rotation such as logrotate. The logger keeps the file open: use copytruncate in the rotation rule or restart the provisioner after rename/create rotation, otherwise it continues writing to the old inode. For production, prefer stdout with journald or runtime-managed rotation.

Runner containers inherit the Docker daemon logging driver unless SHIPFOX_PROVISIONER_DOCKER_LOG_DRIVER is set. A local driver with on-host rotation can be configured as follows:

export SHIPFOX_PROVISIONER_DOCKER_LOG_DRIVER=local
export SHIPFOX_PROVISIONER_DOCKER_LOG_OPTIONS='{"max-size":"10m","max-file":"5"}'

For journald, configure the driver and a tag, then retrieve output with journalctl:

export SHIPFOX_PROVISIONER_DOCKER_LOG_DRIVER=journald
export SHIPFOX_PROVISIONER_DOCKER_LOG_OPTIONS='{"tag":"shipfox-runner/{{.Name}}"}'
journalctl CONTAINER_NAME=<runner-name> --since today

Enable persistent journald storage (Storage=persistent in /etc/systemd/journald.conf) and configure its disk and rotation limits. Remote drivers use the retention and access policy of their durable backend. For local and json-file, docker logs --timestamps --tail 200 <runner-name> works only until cleanup removes the container. Use journald or the remote backend after cleanup.

Failed containers remain for forensic inspection only when both retention settings are greater than zero. The default TTL is one hour and the default count bound is 20. Docker FinishedAt drives TTL cleanup. Missing FinishedAt uses the first observed failure time instead of creation time, so a long-running container is not removed immediately. Terminal inspection failures defer TTL cleanup but remain subject to the count bound, and unknown failure times rank as newly observed for count eviction. Successful exits, dead containers, stale-created containers, and backend termination keep their immediate cleanup behavior.

Forensic output is driver-specific: use docker inspect <runner-name> and docker logs --timestamps --tail 200 <runner-name> for local or json-file, journalctl CONTAINER_NAME=<runner-name> for journald, and the configured durable backend for remote drivers. For a remote Docker daemon, set DOCKER_HOST to the configured SHIPFOX_PROVISIONER_DOCKER_HOST value before running Docker commands; omit it for the local default. Run journalctl on the Docker daemon host, not on the operator workstation. The none driver has no container output.

Template file schema

The templates file can contain shared lookup data, a default fragment, hand-written one-offs, and any number of independent matrix families. This is a complete Docker example with a general fleet, a separate GPU family, and one hand-written template:

vars:
  image_by_os:
    ubuntu22: ghcr.io/shipfoxhq/runner:ubuntu22
    ubuntu24: ghcr.io/shipfoxhq/runner:ubuntu24

defaults:
  labels: [docker]
  target_concurrency: 0

templates:
  docker-local-debug:
    labels: [docker, local-debug]
    image: ghcr.io/shipfoxhq/runner:debug
    cpu: 1
    memory: 2GiB
    max_concurrency: 2
    cost: 1

matrix:
  general:
    axes:
      os: [ubuntu22, ubuntu24]
      cpu: [2, 4]
    template:
      labels: [docker, "${{ os }}", "${{ cpu }}vcpu"]
      image: "${{ vars.image_by_os[os] }}"
      cpu: "${{ cpu }}"
      memory: "${{ cpu * 2.0 }}GiB"
      max_concurrency: 50
      cost: "${{ cpu }}"

  gpu:
    axes:
      cuda: [cuda12, cuda13]
      memory: [16g, 32g]
    template:
      labels: [docker, gpu, "${{ cuda }}", "${{ memory }}"]
      image: "ghcr.io/shipfoxhq/runner:${{ cuda }}"
      cpu: 8
      memory: "${{ memory }}"
      max_concurrency: 10
      cost: 20

The checked-in Docker and EC2 files at apps/provisioner-docker/templates.example.yaml and apps/provisioner-ec2/templates.example.yaml use the same shape. Copy one, replace the placeholder images, AMI IDs, subnets, and security groups, then point SHIPFOX_PROVISIONER_TEMPLATES_FILE at the copy.

Top-level keyTypeDescription
varsmapShared lookup data available to every family. EC2 operators use it for their own AMI and instance-type maps.
defaultsmapA fragment merged under every hand-written and generated template. Maps merge recursively; lists and scalars replace wholesale.
templatesmapHand-written entries for genuine one-offs or per-variant overrides. A hand-written key shadows a generated key and emits a warning.
matrixmapIndependent named families. Each family has its own axes and produces its own generated keys.

Matrix families

KeyTypeDescription
axesmapNamed axes whose values are crossed. An axis can be a list or an expression that returns a list. The default key includes every axis in declaration order.
excludemap listPartial bindings to remove from the cartesian product.
includemap listComplete bindings to append as extra variants. Every declared axis must be present.
letmapPer-variant expressions evaluated in declaration order. Each binding can use axes, vars, and earlier bindings.
keyexpressionOptional explicit key. Without one, the loader builds a key from the family name and each axis value (or object axis name) in declaration order, joining parts with hyphens and escaping separators inside values; generated keys must be unique within the file. Use an explicit key only when you need a different naming scheme.
templatemapProvider-specific template fields rendered for each variant.

Families are independent. Adding a GPU pool means adding a gpu block with GPU axes; it does not mean adding GPU axes to the general fleet. Labels may overlap across families. Matching is subset-based, and the lowest cost wins when several templates can serve the same job, followed by specificity.

Defaults do not append lists. If defaults.subnets contains two general subnets and a GPU family sets subnets: [subnet-gpu], the GPU template has only subnet-gpu. Use a block override whenever a family needs a different list or scalar.

The provider validates every rendered template with a strict schema. For Docker, unknown top-level or template keys now fail with a file-scoped error. This is a breaking upgrade for existing Docker files that relied on mistyped keys being ignored; remove those keys before upgrading. The optional Docker cost field defaults to cpu.

FieldTypeDescription
labelsstring[]Labels the started runner registers with. A template can serve any job whose required labels are all in this list.
imagestringOptional container image to start. Defaults to ghcr.io/shipfoxhq/runner:latest.
cpunumbervCPUs allocated to the container. The default Docker selection cost when cost is omitted.
memorystringMemory allocation, such as 4GiB.
max_concurrencynumberCap on live containers started from this template.
target_concurrencynumberEnrolled, unassigned runners to keep ready without demand. Defaults to 0. Keep it at or below max_concurrency: the provisioner accepts a template where it is higher.
costnumberPositive selection cost. Lower costs are preferred when several templates satisfy the same labels. Docker defaults it to cpu.

Template selection: when several templates satisfy a job's labels, the provisioner picks the lowest cost (or cpu for Docker when cost is omitted), then the most specific. Use a hand-written entry under templates: to override one generated key; the hand-written value wins and the loader logs the shadowed variant.

Was this page helpful?
Edit this page on GitHub

On this page