Skip to content
Marcus Young edited this page Sep 25, 2023 · 12 revisions

Usage

Notes

Token Scope

Creating GitHub personal access token (PAT) for using by self-hosted runner make sure the following scopes are selected:

  • repo (all)
  • admin:org (all) (mandatory for organization-wide runner)
  • admin:enterprise (all) (mandatory for enterprise-wide runner)
  • admin:public_key - read:public_key
  • admin:repo_hook - read:repo_hook
  • admin:org_hook
  • notifications
  • workflow

To use the runner you will need to enable the organization_self_hosted_runners permission with read and write

Back to top

Systemd

Here's an example service definition for systemd:

# Install with:
#   sudo install -m 644 ephemeral-github-actions-runner.service /etc/systemd/system/
#   sudo systemctl daemon-reload
#   sudo systemctl enable ephemeral-github-actions-runner
# Run with:
#   sudo systemctl start ephemeral-github-actions-runner
# Stop with:
#   sudo systemctl stop ephemeral-github-actions-runner
# See live logs with:
#   journalctl -f -u ephemeral-github-actions-runner.service --no-hostname --no-tail
[Unit]
Description=Ephemeral GitHub Actions Runner Container
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop %N
ExecStartPre=-/usr/bin/docker rm %N
ExecStartPre=-/usr/bin/docker pull myoung34/github-runner:latest
ExecStart=/usr/bin/docker run --rm \
                              --env-file /etc/ephemeral-github-actions-runner.env \
                              -e RUNNER_NAME=%H \
                              -v /var/run/docker.sock:/var/run/docker.sock \
                              --name %N \
                              myoung34/github-runner:latest
[Install]
WantedBy=multi-user.target

And an example of the corresponding env file that the service reads from:

# Install with:
#   sudo install -m 600 ephemeral-github-actions-runner.env /etc/
RUNNER_SCOPE=repo
REPO_URL=https://github.com/your-org/your-repo
# Alternate for org scope:
#RUNNER_SCOPE=org
#ORG_NAME=your-org
LABELS=any-custom-labels-go-here
ACCESS_TOKEN=foo-access-token
RUNNER_WORKDIR=/tmp/runner/work
DISABLE_AUTO_UPDATE=1
EPHEMERAL=1

Back to top

Ephemeral Runners

GitHub's hosted runners are completely ephemeral. You can remove all its data without breaking all future jobs.

To achieve the same resilience in a self-hosted runner:

  1. set EPHEMERAL=1 in the container's environment
  2. don't mount a local folder into RUNNER_WORKDIR (to ensure no filesystem persistence)
  3. run the container with --rm (to delete it after termination)
  4. wrap the container execution in a system service that restarts (to start a fresh container after each job)

Back to top

Non-Root Runners

This project runs the container as root by default.

Running as non-root is non-default behavior that is supported via an environment variable RUN_AS_ROOT. Default value is true.

  • If true: preserve old behavior and run as root
  • If true and user is provided with -u (or any orchestrator equiv): error and exit
  • if false: run container as root and assume runner user via gosu
  • if false and user is provided with -u (or any orchestrator equiv): run entire container as runner user

The runner user is runner with uid 1001 and gid 121

If you'd like to run the whole container as non-root:

  • Set the environment variable RUN_AS_ROOT to false
  • Ensure RUNNER_WORKDIR is either not provided (/_work by default) or permissions are correct. the runner user cannot change a directories permissions in entrypoint.sh that it does not have access to
  • add -u runner or -u 1001 to the docker command. In k8s this would be securityContext.runAsUser. Nomad, etc would all do this differently.

Back to top

Actions Workflow

name: Package
on:
  release:
    types: [created]
jobs:
  build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v1
    - name: build packages
      run: make all

Back to top

Docker-Compose

version: '2.3'
services:
  worker:
    image: myoung34/github-runner:latest
    environment:
      REPO_URL: https://github.com/example/repo
      RUNNER_NAME: example-name
      RUNNER_TOKEN: someGithubTokenHere
      RUNNER_WORKDIR: /tmp/runner/work
      RUNNER_GROUP: my-group
      RUNNER_SCOPE: 'repo'
      LABELS: linux,x64,gpu
    security_opt:
      # needed on SELinux systems to allow docker container to manage other docker containers
      - label:disable
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock'
      - '/tmp/runner:/tmp/runner'
      # note: a quirk of docker-in-docker is that this path
      # needs to be the same path on host and inside the container,
      # docker mgmt cmds run outside of docker but expect the paths from within

Back to top

Nomad

job "github_runner" {
  datacenters = ["home"]
  type = "system"
  task "runner" {
    driver = "docker"
    env {
      ACCESS_TOKEN       = "footoken"
      RUNNER_NAME_PREFIX = "myrunner"
      RUNNER_WORKDIR     = "/tmp/github-runner-your-repo"
      RUNNER_GROUP       = "my-group"
      RUNNER_SCOPE       = "org"
      ORG_NAME           = "octokode"
      LABELS             = "my-label,other-label"
    }
    config {
      image = "myoung34/github-runner:latest"
      
      privileged  = true
      userns_mode = "host"
      volumes = [
        "/var/run/docker.sock:/var/run/docker.sock",
        "/tmp/github-runner-your-repo:/tmp/github-runner-your-repo",
      ]
    }
  }
}

Back to top

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: actions-runner
  namespace: runners
spec:
  replicas: 1
  selector:
    matchLabels:
      app: actions-runner
  template:
    metadata:
      labels:
        app: actions-runner
    spec:
      volumes:
      - name: containerdsock
        hostPath:
          path: /run/containerd/containerd.sock
      - name: workdir
        hostPath:
          path: /tmp/github-runner-your-repo
      containers:
      - name: runner
        image: myoung34/github-runner:latest
        env:
        - name: START_DOCKER_SERVICE
          value: "true"
        - name: ACCESS_TOKEN
          valueFrom:
            secretKeyRef:
              name: actions-runner
              key: ACCESS_TOKEN
        - name: RUNNER_SCOPE
          value: "org"
        - name: ORG_NAME
          value: octokode
        - name: LABELS
          value: my-label,other-label
        - name: RUNNER_NAME_PREFIX
          value: foo
        - name: RUNNER_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: RUNNER_WORKDIR
          value: /tmp/github-runner-your-repo
        - name: RUNNER_GROUP
          value: my-group
        volumeMounts:
        - name: containerdsock
          mountPath: /run/containerd/containerd.sock
        - name: workdir
          mountPath: /tmp/github-runner-your-repo
        securityContext:
          privileged: true # Required if you're enabling docker
        resources:
          limits:
            cpu: 2
            memory: 512Mi
          requests:
            cpu: 2
            memory: 256Mi

Bash

Automatically Getting A Token

A runner token can be automatically acquired at runtime if ACCESS_TOKEN (a GitHub personal access token) is a supplied. This uses the GitHub Actions API. e.g.:

docker run -d --restart always --name github-runner \
  -e ACCESS_TOKEN="footoken" \
  -e RUNNER_NAME="foo-runner" \
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \
  -e RUNNER_GROUP="my-group" \
  -e RUNNER_SCOPE="org" \
  -e ORG_NAME="octokode" \
  -e LABELS="my-label,other-label" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  myoung34/github-runner:latest

Back to top

Enterprise Scope

docker run -d --restart always --name github-runner \
  -e ACCESS_TOKEN="footoken" \
  -e RUNNER_NAME="foo-runner" \
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \
  -e RUNNER_GROUP="my-group" \
  -e RUNNER_SCOPE="enterprise" \
  -e ENTERPRISE_NAME="my-enterprise" \
  -e LABELS="my-label,other-label" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  myoung34/github-runner:latest

Back to top

Org Runner

docker run -d --restart always --name github-runner \
  -e RUNNER_NAME_PREFIX="myrunner" \
  -e ACCESS_TOKEN="footoken" \
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \
  -e RUNNER_GROUP="my-group" \
  -e RUNNER_SCOPE="org" \
  -e DISABLE_AUTO_UPDATE="true" \
  -e ORG_NAME="octokode" \
  -e LABELS="my-label,other-label" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  myoung34/github-runner:latest

Back to top

Per-Repo Runner

docker run -d --restart always --name github-runner \
  -e REPO_URL="https://github.com/myoung34/repo" \
  -e RUNNER_NAME="foo-runner" \
  -e RUNNER_TOKEN="footoken" \
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \
  -e RUNNER_GROUP="my-group" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  myoung34/github-runner:latest

Back to top

Shell Wrapper

function github-runner {
    name=github-runner-${1//\//-}
    org=$(dirname $1)
    repo=$(basename $1)
    tag=${3:-latest}
    docker rm -f $name
    docker run -d --restart=always \
        -e REPO_URL="https://github.com/${org}/${repo}" \
        -e RUNNER_TOKEN="$2" \
        -e RUNNER_NAME="linux-${repo}" \
        -e RUNNER_WORKDIR="/tmp/github-runner-${repo}" \
        -e RUNNER_GROUP="my-group" \
        -e LABELS="my-label,other-label" \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -v /tmp/github-runner-${repo}:/tmp/github-runner-${repo} \
        --name $name myoung34/github-runner:latest
}
github-runner your-account/your-repo       AARGHTHISISYOURGHACTIONSTOKEN
github-runner your-account/some-other-repo ARGHANOTHERGITHUBACTIONSTOKEN ubuntu-focal

Back to top

Reusage

This can be propogated to all other approaches

# per repo
docker run -d --restart always --name github-runner \
  -e REPO_URL="https://github.com/myoung34/repo" \
  -e RUNNER_NAME="foo-runner" \
  -e RUNNER_TOKEN="footoken" \
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \
  -e RUNNER_GROUP="my-group" \
  -e CONFIGURED_ACTIONS_RUNNER_FILES_DIR="/actions-runner-files" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  -v /tmp/foo:/actions-runner-files \
  myoung34/github-runner:latest

Back to top

Proxy Support

To run the github runners behind a proxy, you need to pass the proxy parameters required for the GitHub Runner as environment variables. Note: The http:// as prefix is required by the GitHub Runner.

docker run -d --restart always --name github-runner \
  -e https_proxy="http://myproxy:3128" \
  -e http_proxy="http://myproxy:3128" \
  -e RUNNER_NAME_PREFIX="myrunner" \
  # ...
  myoung34/github-runner:latest

Back to top