- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 431
Usage
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
Note that if an org runner needs to be available there may be some settings to toggle such as Allow Public Repositories below.

Prerequisites:
- 
Create a github app and give it a name, but make sure the org owns the app
- Add the permissions in the app for Actions - Read-only,Administration - Read & Write,Metadata - Read-only. If using this with an organization runner, addActions - Read-onlyandMetadata - Read-onlyfor organization permissions.
 
- Add the permissions in the app for 
- Install the app to your organization, you should see it in https://github.com/organizations/{Your organization}/settings/installationsand make sure it has access to all repositories or the one ones you wish to use
- Make note of the application id on the application itself, such as 1313114
- Generate a private key, you'll need the raw contents in PEM format
Next, run something similar to:
export _PRIVATE_KEY_CONTENTS=$(cat ~/Downloads/366b70228e3f46adab23cc16ce5a3cf5.2025-05-23.private-key.pem)
docker run -it \
  -e RUNNER_NAME=testing-an-app \
  -e APP_ID=1313114 \
  -e APP_PRIVATE_KEY="${_PRIVATE_KEY_CONTENTS}" \
  -e APP_LOGIN=OctoKode \
  -e RUNNER_SCOPE=org \
  -e ORG_NAME=OctoKode \
  ghcr.io/myoung34/docker-github-actions-runner:latest
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
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:
- set EPHEMERAL=1in the container's environment
- don't mount a local folder into RUNNER_WORKDIR(to ensure no filesystem persistence)
- run the container with --rm(to delete it after termination)
- wrap the container execution in a system service that restarts (to start a fresh container after each job)
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 trueand user is provided with-u(or any orchestrator equiv): error and exit
- if false: run container as root and assumerunneruser via gosu
- if falseand user is provided with-u(or any orchestrator equiv): run entire container asrunneruser
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_ROOTtofalse
- Ensure RUNNER_WORKDIRis either not provided (/_workby default) or permissions are correct. therunneruser cannot change a directories permissions in entrypoint.sh that it does not have access to
- add -u runneror-u 1001to the docker command. In k8s this would besecurityContext.runAsUser. Nomad, etc would all do this differently.
The runner image can be customized by modifying the config.json file.
If a specific user and group ID are required to align with a user on the host,
they can be set by modifying the user-id and group-id fields:
{
  "user": {
    "user-id": 1001,
    "group-id": 121
  }
}Packages can be added or removed from the image by changing the install field.
An example is the following:
{
  "install": [
    {
      "category": "sdks",
      "source": "apt",
      "packages": [
        "nodejs",
        "python3",
        "openjdk-21-jdk"
      ]
    },
    {
      "category": "clis",
      "source": "script",
      "packages": [
        "aws-cli",
        "github-cli"
      ]
    }
]
}The category name is arbitrary and is used only to document related packages together.
Any number of packages can be specified in each category.
Sources:
- For apt-sourced packages, any package can be installed for the sources configured in sources.sh.
- For script-sourced packages, a function must exist in tools.sh with the nameinstall_<package>.
name: Package
on:
  release:
    types: [created]
jobs:
  build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v1
    - name: build packages
      run: make allversion: '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
      CONFIGURED_ACTIONS_RUNNER_FILES_DIR: /runner/data # Required for persistence
      RUNNER_GROUP: my-group # github enterprise only
      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'
      - '/runner/data:/runner/data' # required for persistence
      - '/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 withinjob "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",
      ]
    }
  }
}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: 256MiA 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:latestdocker 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:latestdocker 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:latestdocker 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
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-focalThis 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:latestTo 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