Building a Custom Self-Hosted GitHub Actions Runner with Docker and Upptime
Learn how to create a Dockerized GitHub Actions runner with this step-by-step guide. Enhance your CI/CD pipeline with a tailored, secure, and efficient runner.
GitHub Actions CI/CD pipelines offer flexibility and integration with GitHub's ecosystem. I recently found myself in a scenario where I needed a custom self-hosted runner for server up-time monitoring. This guide will show you how to build your own GitHub Actions runner using Docker, providing you with a tailored environment for your workflows with an optional step to integrate with Upptime
Prerequisites
- Basic understanding of Docker and GitHub Actions.
- Docker installed on your system.
- A GitHub repository to set up the runner.
Step 1: Creating the Dockerfile
To create this dockerfile, I hacked together various existing Dockerfile implementations I found on Github to create a runner that would work for my use case. There was a lot of trial and error involved which partly motivated me to write this guide.
cat << 'EOF' > Dockerfile
FROM debian:bullseye-slim
# Define arguments for versions and other variables with default values for RUNNER_VERSION
ARG RUNNER_VERSION=latest
ARG CHECKSUM
ARG RUNNER_URL
ARG RUNNER_TOKEN
ARG RUNNER_NAME
ARG LABELS
ARG RUNNER_GROUP
ARG DISABLE_UPDATE
ARG NODE_VERSION=20.8.0
# Fail the build if the CHECKSUM is not provided
RUN if [ -z "\$CHECKSUM" ]; then echo "CHECKSUM argument not provided" && exit 1; fi
# Install necessary packages (curl, tar, libicu, git, ca-certificates)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
tar \
libicu67 \
git \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create a non-root user and switch to it
RUN useradd -m runner
# Set up NVM environment variables for the 'runner' user
ENV NVM_DIR="/home/runner/.nvm"
ENV npm_config_cache="/home/runner/.npm"
USER runner
WORKDIR /home/runner
# Install NVM, Node.js, and update npm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \
&& . $NVM_DIR/nvm.sh \
&& nvm install $NODE_VERSION \
&& nvm use $NODE_VERSION \
&& nvm alias default $NODE_VERSION \
&& npm install -g npm@latest
# Create actions-runner directory and set permissions
WORKDIR /actions-runner
RUN chmod 755 /actions-runner
# Download, verify, and extract the GitHub Actions runner
RUN curl -o actions-runner-linux-x64-$RUNNER_VERSION.tar.gz -L https://github.com/actions/runner/releases/download/v$RUNNER_VERSION/actions-runner-linux-x64-$RUNNER_VERSION.tar.gz \
&& echo "$CHECKSUM actions-runner-linux-x64-$RUNNER_VERSION.tar.gz" | sha256sum -c \
&& tar xzf actions-runner-linux-x64-$RUNNER_VERSION.tar.gz \
&& rm actions-runner-linux-x64-$RUNNER_VERSION.tar.gz
# Ensure the script is executable
RUN chmod +x ./config.sh
# Configure the runner with the provided script line
RUN echo ./config.sh --unattended --url \$RUNNER_URL --token \$RUNNER_TOKEN --name \$RUNNER_NAME ${LABELS:+--labels \$LABELS} ${RUNNER_GROUP:+--runnergroup "\$RUNNER_GROUP"} ${DISABLE_UPDATE:+--disableupdate} | bash
# Set the entrypoint to the run script
ENTRYPOINT ["./run.sh"]
EOF
The Dockerfile includes:
- A Debian base image.
- Necessary packages like curl, tar, libicu, git, and ca-certificates.
- A non-root user setup.
- Node.js and NVM installation.
- The GitHub Actions runner download and setup.
Step 2: Configuring the Runner and GitHub Repository for Upptime
(Skip to Step 3. if you aren't using Upptime and just want the runner build command)
Use the upptime master repository template to create a new repository for your upptime instance
Include all branches is required for upptime to work properly, e.g. gh-pages
Create Github Organization (Optional) only if using custom subdomain
Create a CNAME record with your provider that lines up with your domain/custom subdomain and points to your github pages url (e.g. status.qr-gen.net -> error-try-again.github.io) (Optional)
Select deploy from the branch gh-pages / root, add your domain, enable HTTPs and save
Customize upptimerc.yml to your liking - this is done by editing the file in the repo or cloning the repo and editing locally
Create a personal access token (PAT) in github with repo Read-Write access to Contents, Actions, Issues, Webhooks & Workflows - this will be used to authenticate the runner to your repo and is critical to the runner working properly
Add the token to the upptime repository secrets
Step 3: Building the Docker Image
With your Dockerfile ready, build the Docker image. This step involves passing various arguments like the runner version, checksum, and configuration details.
docker build \
--build-arg RUNNER_VERSION="<runner-version>" \
--build-arg CHECKSUM="<yourchecksum>" \
--build-arg RUNNER_URL="https://github.com/<your-user>/<custom-repo-name>" \
--build-arg RUNNER_TOKEN="<yourtoken>" \
--build-arg RUNNER_NAME="<custom-runner-name>" \
--build-arg LABELS="self-hosted" \
--build-arg RUNNER_GROUP="" \
--build-arg DISABLE_UPDATE="true" \
-t self-hosted-runner .
Step 4: Running the Docker Container
Once the image is built, run it as a detached Docker container. This makes your self-hosted runner operational and ready to execute workflows.
docker run --detach self-hosted-runner
Verification and Troubleshooting
After setting up your runner, verify its registration in your GitHub repository's 'Actions' settings. If you encounter issues, check the Docker logs and ensure all the arguments were correctly passed during the build process. Additionally, check the runner's status in the 'Actions' settings of your repository. If the runner is offline, check the Docker logs for any errors. If you run into other issues, it might be helpful to re-run the Setup CI workflow, pages-build-deployment & any failed jobs, under repo Actions/All Workflows.
References