Portainer Templates logo

Portainer Templates

GitHub Actions Runner GitHub Actions Runner

Stack

Dev Tools

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

Pulls: 65.8M
Architecture: amd64, arm64
Image size: 809 MB
Latest: 2.337.0-ubuntu-noble
User: myoung34
Created: Nov 15, 2019
Updated: 14 hours ago
Status: active

Configuration

Type
Compose
Platform
linux
Image
myoung34/github-runner:2.336.0
Volumes
/runner-state : runnerstate
Env vars
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=trueCONFIGURED_ACTIONS_RUNNER_FILES_DIR=/runner-stateRUNNER_WORKDIR=/tmp/runner/work
Restart
unless-stopped
Source

Standalone Install

Select an install method, to see config/commands for deploying GitHub Actions Runner

Installation method

Install on Portainer

Import all app templates into your Portainer instance, for easy 1-click deploys

  1. Ensure both Docker and Portainer are installed, and up-to-date
  2. Log into your Portainer web UI
  3. Under Settings → App Templates, paste the below URL
  4. Head to Home → App Templates, and the list of apps will show up
  5. 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 demo
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 -d

More install options in our documentation.

Docker Github Actions Runner

Docker Pulls awesome-runners
This will run the new self-hosted github actions runners.

Quick-Start (Examples and Usage) ##

Please see the wiki Please read the contributing guidelines

Included 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 wiki

Notes ##

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 containerd

Docker Artifacts ##

Container BaseSupported ArchitecturesTag RegexDocker TagsDescriptionNotes
ubuntu focalx8664,arm64/\d\.\d{3}\.\d+/ /\d\.\d{3}\.\d+-ubuntu-focal/latest ubuntu-focalThis 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 noblex8664,arm64/\d\.\d{3}\.\d+-ubuntu-noble/ubuntu-nobleThis is the latest build from noble (Rebuilt nightly and on master merges). Tags with -ubuntu-noble are included and created on upstream tags.
ubuntu jammyx8664,arm64/\d\.\d{3}\.\d+-ubuntu-jammy/ubuntu-jammyThis 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-busterDebian 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 bookwormx8664,arm64/\d\.\d{3}\.\d+-debian-bookworm/debian-bookwormThis is the latest build from bookworm (Rebuilt nightly and on master merges). Tags with -debian-bookworm are included and created on upstream tags.
debian sidx8664,arm64/\d\.\d{3}\.\d+-debian-sid/debian-sidThis 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 VariableDescription
RUNASROOTBoolean 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
RUNNERNAMEThe name of the runner to use. Supersedes (overrides) RUNNERNAMEPREFIX
RUNNERNAMEPREFIXA 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
RANDOMRUNNERSUFFIXBoolean 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.
ACCESSTOKENA github PAT to use to generate RUNNERTOKEN dynamically at container start. Not using this requires a valid RUNNERTOKEN
APPIDThe github application ID. Must be paired with APPPRIVATEKEY and should not be used with ACCESSTOKEN or RUNNERTOKEN
APPPRIVATEKEYThe github application private key. Must be paired with APPID and should not be used with ACCESSTOKEN or RUNNERTOKEN
APPLOGINThe 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'.
RUNNERSCOPEThe 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'.
ORGNAMEThe organization name for the runner to register under. Requires RUNNERSCOPE to be 'org'. No default value.
ENTERPRISENAMEThe enterprise name for the runner to register under. Requires RUNNERSCOPE to be 'enterprise'. No default value.
LABELSA comma separated string to indicate the labels. Default is 'default'
REPOURLIf using a non-organization runner this is the full repository url to register under such as 'https://github.com/myoung34/repo'
RUNNERTOKENIf 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.
RUNNERWORKDIRThe 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.
RUNNERGROUPName of the runner group to add this runner to (defaults to the default runner group)
GITHUBHOSTOptional URL of the Github Enterprise server e.g github.mycompany.com. Defaults to github.com.
DISABLEAUTOMATICDEREGISTRATIONOptional flag to disable signal catching for deregistration. Default is false. Any value other than exactly false is considered true. See here
CONFIGUREDACTIONSRUNNERFILESDIRPath to use for runner data. It allows avoiding reregistration each the start of the runner. No default value.
EPHEMERALOptional flag to configure runner with --ephemeral option. Ephemeral runners are suitable for autoscaling.
DISABLEAUTOUPDATEOptional 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.
STARTDOCKERSERVICEOptional flag which automatically starts the docker service if set to true. Useful when using sysbox. Defaults to false.
NODEFAULTLABELSOptional 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.
DEBUGONLYOptional boolean to print debug output but not run any actual registration or runner commands. Used in CI and testing. Default: false
DEBUGOUTPUTOptional boolean to print additional debug output. Default: false
UNSETCONFIGVARSOptional 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 CI
To 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 10

Check 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: 137 means killed, usually out of memory. 126 or 127 means 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 login to 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 no so 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.

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-state kept in the runnerstate volume 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 to repo. Repo | org
  • REPO_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 to miget
  • LABELS, defaults to self-hosted,linux,x64,miget
  • EPHEMERAL, defaults to false. True = runner deregisters after each job (clean-slate; pairs with replicas)
  • DISABLE_AUTO_UPDATE, defaults to true
  • CONFIGURED_ACTIONS_RUNNER_FILES_DIR, defaults to /runner-state
  • RUNNER_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.