Configuration
Paste stack.env.example into Portainer’s Advanced mode environment box and
fill it in. Five values are required; the rest have defaults.
Required
Section titled “Required”REPO_URL
Section titled “REPO_URL”The repository this runner serves — https://github.com/ORG/REPO.
With RUNNER_SCOPE=org, the organisation URL instead. Read
Security first: an org runner
accepts jobs from every repository in the organisation, including ones somebody
adds later.
RUNNER_NAME
Section titled “RUNNER_NAME”How you recognise it under Settings → Actions → Runners. Anything; something
that names the host and the purpose ages well, like ci-box-e2e.
Must be unique across the runners of that repository or organisation.
RUNNER_LABELS
Section titled “RUNNER_LABELS”What a workflow asks for:
runs-on: [self-hosted, linux, x64, your-label]self-hosted, linux and x64 are added by the runner itself; this is the
specific one.
Always set something specific. With only self-hosted, any job in scope
lands here — including jobs this host cannot serve.
RUNNER_TOKEN
Section titled “RUNNER_TOKEN”The registration token, from Settings → Actions → Runners → New self-hosted runner. Valid one hour, single-use; the runner holds its own credentials afterwards.
If the deploy fails with a registration error, the token expired. Fetch a fresh one and set it again — nothing else is lost.
RUNNER_PAT
Section titled “RUNNER_PAT”Optional, and an alternative to RUNNER_TOKEN rather than a companion to it: a
personal access token with repo scope (or admin:org for an org runner),
which the image uses to fetch a registration token whenever it needs one. That
saves refreshing a short-lived token by hand; the trade is a credential that
lives as long as you let it.
Fill one or the other. If both are set, this one wins.
RUNNER_ORG_NAME
Section titled “RUNNER_ORG_NAME”Only read when RUNNER_SCOPE is org or enterprise. Leave it empty for a
repository runner.
RUNNER_WORK_ROOT
Section titled “RUNNER_WORK_ROOT”The runner’s work directory, as an absolute host path. Create it first:
sudo mkdir -p /srv/e2e-ci/_worksudo chown 1000:1000 /srv/e2e-ci/_workTwo things about it:
- It must be unique per stack on the same host. Two runners sharing a work root overwrite each other’s checkouts.
- It appears three times in the compose file — as this variable and as both sides of the volume — and all three have to stay identical. See the identical-paths rule. Get it wrong and Docker mounts empty directories without erroring.
Optional
Section titled “Optional”RUNNER_SCOPE
Section titled “RUNNER_SCOPE”repo (default) or org. See REPO_URL and
Security.
RUNNER_IMAGE
Section titled “RUNNER_IMAGE”Default myoung34/github-runner:2.338.0-ubuntu-noble.
Pin it — a runner that pulls a new image mid-job loses the job. Pick the OS your
workflows were written against: ubuntu-noble is Ubuntu 24.04, which is what
GitHub’s ubuntu-latest resolved to before the Ubuntu 26 migration beginning
19 October 2026. Pinning is also what stops your runner following that
migration silently.
RUNNER_EPHEMERAL
Section titled “RUNNER_EPHEMERAL”Default empty, which means off.
Empty keeps the container between jobs, so pulled images, package stores and browser downloads stay warm. The cost is that state leaks from one job to the next.
On gives a clean container per job. Correct for a public repository or one that takes fork pull requests, at the price of re-downloading everything each run.
PRUNE_KEEP
Section titled “PRUNE_KEEP”Default 24h. How much to keep: images, build cache and stopped containers
younger than this survive.
Do not set it very low. Keeping a day’s worth of images is what makes a self-hosted runner faster than a hosted one; pruning aggressively means every job re-downloads what the last one used.
PRUNE_EVERY_SECONDS
Section titled “PRUNE_EVERY_SECONDS”Default 3600.
PRUNE_VOLUMES
Section titled “PRUNE_VOLUMES”Default false, and think before changing it.
true runs docker volume prune -af, which on a shared host destroys other
stacks’ data — a volume whose container has been removed counts as unused.
Only sensible on a host that runs nothing but runners.
PRUNE_IMAGE
Section titled “PRUNE_IMAGE”Default docker:29-cli. The image the prune service runs; it needs nothing but
the Docker CLI.
Resetting a host
Section titled “Resetting a host”cleanup.sh in the repository runs on the host, reports by default and changes
nothing until you name a scope:
./cleanup.sh # what is using disk, and what each flag would do./cleanup.sh --containers --images # the usual reset./cleanup.sh --work /srv/e2e-ci/_work # clear a work root, keeping _tool and _actionsKEEP=24h ./cleanup.sh --all # spare anything younger than 24h