GitHub Actions Runner
Stack
A self-hosted GitHub Actions runner: your workflows, your hardware, zero per-minute billing. Uses myoung34/github-runner (the de-facto env-driven runner image) with PAT-based auto-registration.
Image details
Configuration
TypeComposelinuxmyoung34/github-runner:2.336.0/runner-state : runnerstateACCESS_TOKEN=${ACCESS_TOKEN}RUNNER_SCOPE=${RUNNER_SCOPE:-repo}REPO_URL=${REPO_URL:-}ORG_NAME=${ORG_NAME:-}RUNNER_NAME_PREFIX=${RUNNER_NAME_PREFIX:-miget}LABELS=${LABELS:-self-hosted,linux,x64,miget}EPHEMERAL=${EPHEMERAL:-false}DISABLE_AUTO_UPDATE=trueCONFIGURED_ACTIONS_RUNNER_FILES_DIR=/runner-stateRUNNER_WORKDIR=/tmp/runner/workunless-stoppedTemplate by deployable-sh·Source
Report issueStandalone Install
Select an install method, to see config/commands for deploying GitHub Actions Runner
Install on Portainer
Import all app templates into your Portainer instance, for easy 1-click deploys
- Ensure both Docker and Portainer are installed, and up-to-date
- Log into your Portainer web UI
- Under Settings → App Templates, paste the below URL
- Head to Home → App Templates, and the list of apps will show up
- Select GitHub Actions Runner, fill in any config options, and hit Deploy
Template Import URL
https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json
Show Me
Original stackfile
The compose file this template deploys, straight from its repo:
name: github-runner
services:
runner:
image: myoung34/github-runner:2.336.0
restart: unless-stopped
environment:
ACCESS_TOKEN: ${ACCESS_TOKEN}
RUNNER_SCOPE: ${RUNNER_SCOPE:-repo}
REPO_URL: ${REPO_URL:-}
ORG_NAME: ${ORG_NAME:-}
RUNNER_NAME_PREFIX: ${RUNNER_NAME_PREFIX:-miget}
LABELS: ${LABELS:-self-hosted,linux,x64,miget}
EPHEMERAL: ${EPHEMERAL:-false}
DISABLE_AUTO_UPDATE: "true"
CONFIGURED_ACTIONS_RUNNER_FILES_DIR: /runner-state
RUNNER_WORKDIR: /tmp/runner/work
volumes:
- runnerstate:/runner-state
volumes:
runnerstate:
Or deploy it directly from the source:
git clone https://github.com/deployable-sh/stacks
cd stacks
docker compose -f github-runner/compose.yaml up -dMore install options in our documentation.
Docker Github Actions Runner
This will run the new self-hosted github actions runners.
Quick-Start (Examples and Usage) ##
Please see the wiki Please read the contributing guidelinesIncluded software and configuration ##
While this project is not perfectly 1:1 with the software upstream, the included packages etc are available here. Documentation can be found in the wikiNotes ##
Security ###
It is known that environment variables are not safe from exfiltration. If you are using this runner make sure that any workflow changes are gated by a verification process (in the actions settings) so that malicious PR's cannot exfiltrate these.Docker Support ###
Please note that while this runner installs and allows docker, github actions itself does not support using docker from a self hosted runner yet. For more information:- https://github.com/actions/runner/issues/406
- https://github.com/actions/runner/issues/367
Also, some GitHub Actions Workflow features, like Job Services, won't be usable and will result in an error.
Containerd Support ###
Currently runners do not support containerdDocker Artifacts ##
| Container Base | Supported Architectures | Tag Regex | Docker Tags | Description | Notes |
|---|---|---|---|---|---|
| ubuntu focal | x8664,arm64 | /\d\.\d{3}\.\d+/ /\d\.\d{3}\.\d+-ubuntu-focal/ | latest ubuntu-focal | This is the latest build (Rebuilt nightly and on master merges). Tags without an OS name are included. Tags with -ubuntu-focal are included and created on upstream tags. | |
| ubuntu noble | x8664,arm64 | /\d\.\d{3}\.\d+-ubuntu-noble/ | ubuntu-noble | This is the latest build from noble (Rebuilt nightly and on master merges). Tags with -ubuntu-noble are included and created on upstream tags. | |
| ubuntu jammy | x8664,arm64 | /\d\.\d{3}\.\d+-ubuntu-jammy/ | ubuntu-jammy | This is the latest build from jammy (Rebuilt nightly and on master merges). Tags with -ubuntu-jammy are included and created on upstream tags. | There is currently an issue with jammy from inside a 20.04LTS host which is why this is not latest |
| debian buster (now deprecated) | x8664,arm64 | /\d\.\d{3}\.\d+-debian-buster/ | debian-buster | Debian buster is now deprecated. The packages for arm v7 are in flux and are wildly causing build failures (git as well as liblttng-ust#. Tags with -debian-buster are included and created on upstream tags. | |
| debian bookworm | x8664,arm64 | /\d\.\d{3}\.\d+-debian-bookworm/ | debian-bookworm | This is the latest build from bookworm (Rebuilt nightly and on master merges). Tags with -debian-bookworm are included and created on upstream tags. | |
| debian sid | x8664,arm64 | /\d\.\d{3}\.\d+-debian-sid/ | debian-sid | This is the latest build from sid (Rebuilt nightly and on master merges). Tags with -debian-sid are included and created on upstream tags. |
These containers are built via Github actions that copy the dockerfile, changing the
FROM and building to provide simplicity.Environment Variables ##
| Environment Variable | Description |
|---|---|
RUNASROOT | Boolean to run as root. If true: will run as root. If True and the user is overridden it will error. If any other value it will run as the runner user and allow an optional override. Default is true |
RUNNERNAME | The name of the runner to use. Supersedes (overrides) RUNNERNAMEPREFIX |
RUNNERNAMEPREFIX | A prefix for runner name (See RANDOMRUNNERSUFFIX for how the full name is generated). Note: will be overridden by RUNNERNAME if provided. Defaults to github-runner |
RANDOMRUNNERSUFFIX | Boolean to use a randomized runner name suffix (preceded by RUNNERNAMEPREFIX). Will use a 13 character random string by default. If set to a value other than true it will attempt to use the contents of /etc/hostname or fall back to a random string if the file does not exist or is empty. Note: will be overridden by RUNNERNAME if provided. Defaults to true. |
ACCESSTOKEN | A github PAT to use to generate RUNNERTOKEN dynamically at container start. Not using this requires a valid RUNNERTOKEN |
APPID | The github application ID. Must be paired with APPPRIVATEKEY and should not be used with ACCESSTOKEN or RUNNERTOKEN |
APPPRIVATEKEY | The github application private key. Must be paired with APPID and should not be used with ACCESSTOKEN or RUNNERTOKEN |
APPLOGIN | The github application login id. Can be paired with APPID and APPPRIVATEKEY if default value extracted from REPOURL or ORGNAME is not correct. Note that no default is present when RUNNERSCOPE is 'enterprise'. |
RUNNERSCOPE | The scope the runner will be registered on. Valid values are repo, org and ent. For 'org' and 'enterprise', ACCESSTOKEN is required and REPOURL is unnecessary. If 'org', requires ORGNAME; if 'ent', requires ENTERPRISENAME. Default is 'repo'. |
ORGNAME | The organization name for the runner to register under. Requires RUNNERSCOPE to be 'org'. No default value. |
ENTERPRISENAME | The enterprise name for the runner to register under. Requires RUNNERSCOPE to be 'enterprise'. No default value. |
LABELS | A comma separated string to indicate the labels. Default is 'default' |
REPOURL | If using a non-organization runner this is the full repository url to register under such as 'https://github.com/myoung34/repo' |
RUNNERTOKEN | If not using a PAT for ACCESSTOKEN this will be the runner token provided by the Add Runner UI (a manual process). Note: This token is short lived and will change frequently. ACCESSTOKEN is likely preferred. |
RUNNERWORKDIR | The working directory for the runner. Runners on the same host should not share this directory. Default is '/work'. This must match the source path for the bind-mounted volume at RUNNERWORKDIR, in order for container actions to access files. |
RUNNERGROUP | Name of the runner group to add this runner to (defaults to the default runner group) |
GITHUBHOST | Optional URL of the Github Enterprise server e.g github.mycompany.com. Defaults to github.com. |
DISABLEAUTOMATICDEREGISTRATION | Optional flag to disable signal catching for deregistration. Default is false. Any value other than exactly false is considered true. See here |
CONFIGUREDACTIONSRUNNERFILESDIR | Path to use for runner data. It allows avoiding reregistration each the start of the runner. No default value. |
EPHEMERAL | Optional flag to configure runner with --ephemeral option. Ephemeral runners are suitable for autoscaling. |
DISABLEAUTOUPDATE | Optional environment variable to disable auto updates. Auto updates are enabled by default to preserve past behavior. Any value is considered truthy and will disable them. |
STARTDOCKERSERVICE | Optional flag which automatically starts the docker service if set to true. Useful when using sysbox. Defaults to false. |
NODEFAULTLABELS | Optional environment variable to disable adding the default self-hosted, platform, and architecture labels to the runner. Any value is considered truthy and will disable them. |
DEBUGONLY | Optional boolean to print debug output but not run any actual registration or runner commands. Used in CI and testing. Default: false |
DEBUGOUTPUT | Optional boolean to print additional debug output. Default: false |
UNSETCONFIGVARS | Optional flag to unset all configuration environment variables after runner setup but before starting the runner. This prevents these variables from leaking into the workflow environment. Set to 'true' to enable. Defaults to 'false' for backward compatibility. |
Tests ##
Tests are written in goss for general assertions. It's expected that all pull-requests have relevant assertions in order to be merged.Prereqs: Ensure that docker, goss and dgoss are set up Note: while testing locally works, github actions will test all variations of operating systems and supported architectures.
The test file expects the image to test as an environment variable
GH_RUNNER_IMAGE to assist in CITo test:
$ # need to set minimum vars for the goss test interpolation
$ echo "os: ubuntu" >goss_vars.yaml
$ echo "oscodename: focal" >>goss_vars.yaml
$ echo "arch: x86_64" >>goss_vars.yaml
$ docker build -t my-base-test -f Dockerfile.base .
$ # Use the base image in your final
$ sed -i.bak 's/^FROM.*/FROM my-base-test/g' Dockerfile
$ docker build -t my-full-test -f Dockerfile .
$ # Run the full test from Dockerfile.base on the current git HEAD
$ GOSS_VARS=goss_vars.yaml GOSS_FILE=goss_full.yaml GOSS_SLEEP=1 dgoss run --entrypoint /usr/bin/sleep \
-e DEBUG_ONLY=true \
-e RUNNER_NAME=huzzah \
-e REPO_URL=https://github.com/myoung34/docker-github-actions-runner \
-e RUN_AS_ROOT=true \
-e RUNNER_NAME_PREFIX=asdf \
-e ACCESS_TOKEN=1234 \
-e APP_ID=5678 \
-e APP_PRIVATE_KEY=2345 \
-e APP_LOGIN=SOMETHING \
-e RUNNER_SCOPE=org \
-e ORG_NAME=myoung34 \
-e ENTERPRISE_NAME=emyoung34 \
-e LABELS=blue,green \
-e RUNNER_TOKEN=3456 \
-e RUNNER_WORKDIR=/tmp/a \
-e RUNNER_GROUP=wat \
-e GITHUB_HOST=github.example.com \
-e DISABLE_AUTOMATIC_DEREGISTRATION=true \
-e EPHEMERAL=true \
-e DISABLE_AUTO_UPDATE=true \
my-full-test 10Check the logs first
Nine times out of ten the logs tell you exactly what went wrong.
- In Portainer, go to Containers, click the container, then Logs. Or run
docker logs <container> - Exit codes help too:
137means killed, usually out of memory.126or127means the command inside the image is broken.
Image won't pull
Test the pull directly on the host: docker pull myoung34/github-runner:2.336.0
- "manifest unknown" means the tag no longer exists.
- "toomanyrequests" is the Docker Hub rate limit. Log in with
docker loginto raise it. - "no space left on device" means a full disk. Reclaim space with
docker system prune
"exec format error"
This means the image was built for a different CPU architecture than your server.
- This image supports:
amd64, arm64 - Check yours with
uname -m: x86_64 is amd64, aarch64 is arm64. Raspberry Pi and other ARM boards are the usual culprits.
Container keeps restarting
The unless-stopped restart policy relaunches the app after every crash, so the real error can scroll past.
- Check the logs right after a restart, the last few lines before it died are the useful ones.
- Get the exit code with
docker inspect <container> --format '{{.State.ExitCode}}' - Still stuck? Redeploy once with the restart policy set to
noso the failure stays visible.
Stack won't deploy
Compose stacks fail fast on small mistakes, and Portainer shows the reason just above the editor.
- YAML only accepts spaces for indentation, a single tab breaks the whole file.
Raise an issue
Found something which isn't working as it should? Here's how to report it.
- Bug within the app: Open an issue within runner's repo
- Template not working: Open an issue on deployable-sh/stacks
- This website not working: Open an issue on lissy93/portainer-templates
A Compose stack
GitHub Actions Runner is a Compose stack, a set of containers defined in one file and brought up together by Portainer, then started and stopped as a single app.
The app image
An image is the app packed up ready to go, everything GitHub Actions Runner needs bundled into one download. This template pulls myoung34/github-runner:2.336.0, which Docker fetches once (about 809 MB) and then starts your own copy from.
Where the image comes from
Docker pulls its images from registries, public libraries of ready-built apps. GitHub Actions Runner's comes from Docker Hub, published by myoung34.
Version tags
The bit after the colon in the image name is the version tag. This one pins 2.336.0, so every redeploy gives you that exact build until you bump it yourself.
Which machines it runs on
Every image is built for particular CPU types. This one ships for amd64, arm64, so it runs on both regular x86 servers and ARM boards like a Raspberry Pi.
Volumes
A volume is where GitHub Actions Runner keeps its files so they survive an update or a restart. Without one, anything it saves would sit inside the container and vanish the moment it's recreated. This template mounts:
/runner-statekept in therunnerstatevolume Docker manages
Environment variables
Environment variables are the settings you hand over when you deploy, things like a password or a timezone. GitHub Actions Runner takes 10 of them, all with defaults you can leave alone or tweak:
ACCESS_TOKEN, pulled from your own environment. Fine-grained or classic PAT.RUNNER_SCOPE, defaults torepo. Repo | orgREPO_URL, pulled from your own environment. For repo scope:ORG_NAME, pulled from your own environment. For org scope (set RUNNERSCOPE=org and leave REPOURL empty):RUNNER_NAME_PREFIX, defaults tomigetLABELS, defaults toself-hosted,linux,x64,migetEPHEMERAL, defaults tofalse. True = runner deregisters after each job (clean-slate; pairs with replicas)DISABLE_AUTO_UPDATE, defaults totrueCONFIGURED_ACTIONS_RUNNER_FILES_DIR, defaults to/runner-stateRUNNER_WORKDIR, defaults to/tmp/runner/work
Restart policy
The restart policy here is unless-stopped, so Docker restarts GitHub Actions Runner after a crash or reboot, but leaves it off when you stop it on purpose. You can change this on the deploy screen. The choices are no (never restart), on-failure (only after a crash), unless-stopped (restart unless you stop it), and always (bring it back no matter what).
Networking
Nothing custom is set, so GitHub Actions Runner sits on Docker's default bridge network: its own private space that reaches the outside world only through the ports it publishes.
Container name
Once it's deployed, Portainer names the container runner. That's what you'll spot in the containers list and use in commands like docker logs runner.
Platform
The platform is linux, the kind of system the container is built to run on. Docker and Portainer handle this on a normal Linux server.
Portainer app templates
Zooming out, this whole page comes from a Portainer app template: a short recipe telling Portainer how to set GitHub Actions Runner up. Add the template list to Portainer once, then deploying GitHub Actions Runner is a click rather than a wall of config.