Why self-hosted runners are worth it
If your builds are slow, your workflow uses special tools, or you need access to an internal network, a self-hosted GitHub Actions runner can be a game-changer. Instead of relying on GitHub-hosted runners (which are shared and time-limited), you run jobs on your own Linux machine. This tutorial shows how to set up a runner on Ubuntu Server with a clean, secure approach: a dedicated user, a systemd service, and basic hardening tips.
What you need before you start
You’ll need: (1) an Ubuntu Server 22.04/24.04 machine (VM or bare metal), (2) outbound internet access to GitHub, (3) a GitHub repository or organization where you can register runners, and (4) sudo privileges on the Linux host. For best results, use a separate machine or VM for CI jobs—treat it as disposable infrastructure.
Step 1: Update the server and install prerequisites
First, patch the system and install common dependencies used by build pipelines. Run the following commands:
sudo apt update && sudo apt -y upgrade
sudo apt -y install curl tar git ca-certificates
If your workflows build containers, install Docker later (and consider isolating it). For now, keep the base runner simple.
Step 2: Create a dedicated runner user
Avoid running CI as your personal account or as root. Create a dedicated user and a working directory:
sudo adduser --disabled-password --gecos "" actions
sudo mkdir -p /opt/actions-runner
sudo chown -R actions:actions /opt/actions-runner
This helps with least privilege and keeps runner files in a predictable location for maintenance.
Step 3: Download the GitHub Actions runner
Switch to the runner user and download the latest Linux x64 runner package. You can find the current version on GitHub’s official runner releases page, but the process is always the same:
sudo -iu actions
cd /opt/actions-runner
curl -o actions-runner-linux-x64.tar.gz -L https://github.com/actions/runner/releases/latest/download/actions-runner-linux-x64-2.0.0.tar.gz
tar xzf actions-runner-linux-x64.tar.gz
Note: the filename in the URL can change as new versions are released. If you get a 404 error, open the releases page and copy the exact download link for Linux x64.
Step 4: Register the runner with your repo or organization
In GitHub, go to your repository: Settings > Actions > Runners > New self-hosted runner. Choose Linux, and GitHub will display a short set of commands including a registration token.
Back on your server (still as the actions user), run the configuration script using the URL and token GitHub provides:
./config.sh --url https://github.com/OWNER/REPO --token YOUR_TOKEN
When prompted, set a clear runner name (for example, ubuntu-ci-01) and add labels that match your use case (like linux, self-hosted, docker, gpu). Labels let you target specific runners in workflows.
Step 5: Install the runner as a systemd service
Running the runner in a terminal works, but it’s not reliable. Install it as a service so it starts on boot and restarts on failure:
sudo ./svc.sh install
sudo ./svc.sh start
Then verify status:
sudo ./svc.sh status
Within a minute, the runner should show as Idle in GitHub under Actions runners.
Step 6: Update a workflow to use your self-hosted runner
In your workflow YAML, set runs-on to include self-hosted plus any labels you assigned:
runs-on: [self-hosted, linux]
If you want to guarantee the job lands on a specific capability (like Docker), use a custom label such as docker and specify it in runs-on.
Security and hardening tips (don’t skip these)
A self-hosted runner executes code from your repository, including pull requests if you allow it. Treat it like a production entry point. Use these practical safeguards: (1) run on a dedicated VM, (2) restrict which branches and events can use the runner, (3) avoid running untrusted fork PRs on self-hosted runners, and (4) keep the OS patched.
Also consider network segmentation: if the runner can reach internal services, use firewall rules so it can only access what it truly needs. If you install Docker, be careful with granting the runner user access to the Docker socket—Docker can effectively become root on the host. For higher-risk environments, run builds inside isolated containers or ephemeral VMs.
Troubleshooting common problems
Runner is offline: check service status (sudo ./svc.sh status) and logs with journalctl -u actions.runner.* -n 200 --no-pager. Reboot-safe service configuration usually fixes “works in terminal, fails after reboot” issues.
Token expired: registration tokens are short-lived. Generate a new token in GitHub and re-run ./config.sh after removing the old configuration (./config.sh remove).
Jobs stuck waiting: your workflow’s runs-on labels must match the runner labels exactly. If the workflow requires [self-hosted, linux, docker] but your runner is only labeled linux, GitHub will keep the job queued.
Conclusion
With a self-hosted GitHub Actions runner on Linux, you can speed up CI/CD, use specialized build tools, and keep deployment workflows closer to your infrastructure. The key is to set it up cleanly (dedicated user and systemd service) and to treat security as part of the installation—not an afterthought.
Comments
Post a Comment