├── .gitignore ├── .env.example ├── start.sh ├── README.md ├── .github └── workflows │ ├── update-version.yml │ └── continuous-deployment.yml ├── docker-compose.yml └── Dockerfile /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | run.sh 3 | stack.yml 4 | access-token 5 | github_access-token -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Github organisation name. 2 | # e.g. ORGANIZATION=moveyourdigital 3 | ORGANIZATION= 4 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ORGANIZATION=$ORGANIZATION 4 | REG_TOKEN=$(curl -sX POST -H "Authorization: token $(cat $ACCESS_TOKEN_FILE)" https://api.github.com/orgs/${ORGANIZATION}/actions/runners/registration-token | jq .token --raw-output) 5 | 6 | cd /home/docker/actions-runner 7 | 8 | echo "Configuring ephemeral runner..." 9 | ./config.sh --url https://github.com/${ORGANIZATION} --token ${REG_TOKEN} --ephemeral --disableupdate 10 | 11 | cleanup() { 12 | echo "Removing runner..." 13 | ./config.sh remove --token ${REG_TOKEN} 14 | } 15 | 16 | trap 'cleanup; exit 130' INT 17 | trap 'cleanup; exit 143' TERM 18 | 19 | echo "Beginning run script..." 20 | ./run.sh & wait $! 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions self-hosted Docker Swarm runner 2 | 3 | This Docker image runs a self-hosted GitHub Actions Runner. 4 | 5 | **Note:** this image starts up [ephemeral runners](https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling) so they get deregistered after each job finishes. 6 | 7 | ### Usage 8 | 1. Clone this repository 9 | 2. Copy `.env.example` to `.env` 10 | 3. Fill up `ORGANIZATION` with your organization name 11 | 4. Get a [GitHub access token](https://github.com/settings/tokens) 12 | 5. Create a secret `access-token` (or whatever) with your newly created token 13 | 6. Point `ACCESS_TOKEN_FILE` to the secret path `/run/secrets/access-token` 14 | 7. Run 15 | 16 | If you wish to run it locally, use docker compose and create a file named `access-token` in the root of this project with the token in it. -------------------------------------------------------------------------------- /.github/workflows/update-version.yml: -------------------------------------------------------------------------------- 1 | name: Update Version 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | update-version: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - id: fetch-latest-release 18 | uses: thebritican/fetch-latest-release@v2.0.0 19 | with: 20 | github_token: ${{ github.token }} 21 | repo_path: actions/runner 22 | 23 | - id: commit 24 | run: | 25 | TAG=${{ steps.fetch-latest-release.outputs.tag_name }} 26 | sed -i.bak "s/ARG RUNNER_VERSION.*/ARG RUNNER_VERSION=\"${TAG:1}\"/" Dockerfile 27 | if [[ `git status -uno --porcelain` ]]; then 28 | git config --global user.name 'lightningspirit [Automated 🤖]' 29 | git config --global user.email 'lightningspirit@users.noreply.github.com' 30 | git commit -am "Bump to $TAG" 31 | git push 32 | fi 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | secrets: 2 | # Note: Github personal access token 3 | # Not the token for registering self-hosted runner 4 | github_access-token: 5 | file: ./access-token 6 | 7 | services: 8 | socket: 9 | image: tecnativa/docker-socket-proxy 10 | volumes: 11 | - /var/run/docker.sock:/var/run/docker.sock:ro 12 | environment: 13 | AUTH: 1 14 | BUILD: 1 15 | CONTAINERS: 1 16 | EXEC: 1 17 | IMAGES: 1 18 | INFO: 1 19 | SESSION: 1 20 | CONFIGS: 1 21 | healthcheck: 22 | # Test on docker healthcheck endpoint 23 | test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:2375/v1.35/_ping" ] 24 | interval: 10s 25 | timeout: 10s 26 | retries: 3 27 | start_period: 30s 28 | start_interval: 2s 29 | 30 | runner: 31 | build: 32 | context: . 33 | secrets: 34 | - github_access-token 35 | env_file: .env 36 | environment: 37 | DOCKER_HOST: socket:2375 38 | ACCESS_TOKEN_FILE: /run/secrets/github_access-token 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # base 2 | FROM ubuntu:22.04 3 | 4 | # set the github runner version 5 | ARG RUNNER_VERSION="2.328.0" 6 | 7 | ENV TZ=Europe/Lisbon 8 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 9 | ARG DEBIAN_FRONTEND=noninteractive 10 | 11 | # install python and the packages the your code depends on along with jq so we can parse JSON 12 | # add additional packages as necessary 13 | RUN set -ex; \ 14 | apt-get update; \ 15 | apt-get upgrade -y; \ 16 | apt-get install -y --no-install-recommends \ 17 | git git-lfs curl jq gnupg lsb-release build-essential libssl-dev libffi-dev python3 python3-venv python3-dev apt-transport-https ca-certificates curl software-properties-common; 18 | 19 | RUN useradd -m docker; \ 20 | usermod -a -G root docker; 21 | 22 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -; \ 23 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable"; \ 24 | apt-get update; \ 25 | apt-get install docker-ce-cli -y; 26 | 27 | # cd into the user directory, download and unzip the github actions runner 28 | RUN cd /home/docker && mkdir actions-runner && cd actions-runner \ 29 | && curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \ 30 | && tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \ 31 | && rm ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz 32 | 33 | # install some additional dependencies 34 | RUN chown -R docker ~docker && /home/docker/actions-runner/bin/installdependencies.sh 35 | 36 | # copy over the start.sh script 37 | COPY start.sh start.sh 38 | 39 | # make the script executable 40 | RUN chmod +x start.sh 41 | 42 | # since the config and run script for actions are not allowed to be run by root, 43 | # set the user to "docker" so all subsequent commands are run as the docker user 44 | USER docker 45 | 46 | # set the entrypoint to the start.sh script 47 | ENTRYPOINT ["./start.sh"] 48 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [master, main] 7 | tags: ['*'] 8 | 9 | permissions: 10 | contents: read 11 | packages: read 12 | 13 | jobs: 14 | deployment: 15 | runs-on: ubuntu-latest 16 | # Multi-platform builds are achieved using QEMU for emulation and Buildx for building images 17 | # across multiple architectures. The platforms are specified in the docker/build-push-action step. 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - uses: docker/setup-qemu-action@v3 25 | - uses: docker/setup-buildx-action@v3 26 | 27 | - id: meta 28 | run: | 29 | REPO="${{ github.repository }}" 30 | if [[ "${GITHUB_REF}" == refs/heads/main || "${GITHUB_REF}" == refs/heads/master ]]; then 31 | echo "tags=$REPO:latest" >> $GITHUB_OUTPUT 32 | elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then 33 | TAG_NAME=${GITHUB_REF#refs/tags/} 34 | if git ls-remote --exit-code origin main; then 35 | git fetch origin main 36 | MAIN_COMMIT=$(git rev-parse origin/main) 37 | elif git ls-remote --exit-code origin master; then 38 | git fetch origin master 39 | MAIN_COMMIT=$(git rev-parse origin/master) 40 | else 41 | echo "Error: Neither 'main' nor 'master' branch exists in the repository." >&2 42 | exit 1 43 | fi 44 | if [ "$GITHUB_SHA" = "$MAIN_COMMIT" ]; then 45 | echo "tags=$REPO:$TAG_NAME,$REPO:latest" >> $GITHUB_OUTPUT 46 | else 47 | echo "tags=$REPO:$TAG_NAME" >> $GITHUB_OUTPUT 48 | fi 49 | fi 50 | 51 | - uses: docker/login-action@v3 52 | with: 53 | username: ${{ secrets.DOCKERHUB_USERNAME }} 54 | password: ${{ secrets.DOCKERHUB_TOKEN }} 55 | 56 | - uses: docker/build-push-action@v5 57 | with: 58 | context: . 59 | platforms: linux/amd64,linux/arm64 60 | push: true 61 | tags: ${{ steps.meta.outputs.tags }} 62 | --------------------------------------------------------------------------------