Self-Hosted Runners and Agents
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Lesson: Self-Hosted Runners and Agents in YAML Pipelines
Introduction: Why Runners Matter
In the world of modern software development, automation is the backbone of delivery. We rely on CI/CD pipelines to build, test, and deploy our code, but those pipelines do not run on thin air. They require compute resources—environments where shell commands are executed, binaries are compiled, and containers are orchestrated. While most cloud-based platforms provide "managed" or "hosted" agents out of the box, there comes a point in every growing organization where these managed solutions fall short. This is where self-hosted runners and agents enter the picture.
A self-hosted runner (or agent) is a machine that you own, manage, and configure to run your pipeline jobs. Instead of relying on the shared, multi-tenant infrastructure provided by your CI/CD platform provider, you dedicate a specific server, virtual machine, or container cluster to handle your workload. This shift in architecture provides you with granular control over the environment, security, and performance of your build processes. Understanding how to design, implement, and maintain these agents is a critical skill for any DevOps engineer or systems architect who needs to move beyond the constraints of standard cloud offerings.
Callout: Managed vs. Self-Hosted Runners Managed runners are ephemeral, pre-configured environments provided by the CI/CD platform. They are excellent for standard workflows but lack customization. Self-hosted runners require manual maintenance but offer total control over hardware, software dependencies, and network access. Choosing between them is a trade-off between operational overhead and environmental flexibility.
Understanding the Architecture of an Agent
To implement self-hosted runners effectively, you must first understand what they actually do. At its core, a runner is a lightweight piece of software that acts as an intermediary between your CI/CD platform and your infrastructure. The runner constantly polls the platform's API for new jobs, downloads the necessary code, executes the defined steps in your YAML configuration, and reports the results back to the server.
The Lifecycle of a Job on a Self-Hosted Runner
- Registration: You install the runner software on your machine and register it with your CI/CD platform using a token.
- Polling: The runner maintains a persistent connection to the platform, waiting for a signal that a job has been queued.
- Assignment: When a job matches the runner's tags or pool requirements, the platform assigns the job to that specific runner.
- Execution: The runner checks out the source code, installs necessary dependencies (or uses pre-installed ones), and executes the tasks defined in your YAML file.
- Reporting: Once the job finishes, the runner streams the output logs back to the platform and cleans up the working directory.
- Idle State: The runner returns to an idle state, ready for the next job in the queue.
Use Cases for Self-Hosted Runners
Why would you choose to manage your own infrastructure instead of letting the platform provider do it? There are several compelling scenarios where self-hosted runners are not just a preference, but a necessity.
1. Networking and Security Requirements
Many organizations operate within private networks, such as on-premises data centers or Virtual Private Clouds (VPCs) with strict firewall rules. Managed runners often exist outside these networks, meaning they cannot access internal databases, private package registries, or internal testing environments. By deploying a runner inside your private network, you can securely perform builds that interact with sensitive internal resources without exposing them to the public internet.
2. Specialized Hardware and Software
Standard cloud runners often come with a generic Linux or Windows environment. If your application requires specific hardware—such as high-end GPUs for machine learning model training, specialized hardware drivers, or specific versions of legacy software—managed runners will not suffice. A self-hosted runner allows you to provision a machine that is perfectly tuned for your specific compilation or testing requirements.
3. Performance and Cost Optimization
If you have a massive volume of builds, the cumulative cost of per-minute billing on managed runners can become astronomical. Conversely, if your builds are extremely long-running, you might hit the maximum execution time limits enforced by cloud providers. A self-hosted runner allows you to "right-size" your compute resources, utilizing reserved instances or on-premises hardware to achieve better performance for a fraction of the cost.
4. Deterministic Build Environments
Managed runners are often updated by the platform provider, which can lead to unexpected breakages when a tool version is upgraded. By managing your own runners, you control the exact state of the environment. You can ensure that every build runs on the exact same OS kernel, the same version of Node.js, and the same system libraries, providing a level of consistency that is hard to achieve with shared environments.
Implementing a Self-Hosted Runner: A Step-by-Step Guide
While specific commands vary slightly between platforms (like GitHub Actions vs. GitLab CI), the general process is universal. Let’s walk through the implementation of a generic runner on a Linux server.
Step 1: Provisioning the Environment
Before installing the agent, ensure your host machine is prepared. This means installing necessary system updates and ensuring the host has enough memory and disk space to handle your largest builds.
Tip: Resource Allocation Always over-provision your disk space. Runners often generate significant logs and build artifacts. Running out of disk space during a compilation job is a common cause of mysterious pipeline failures.
Step 2: Authentication and Registration
Your CI/CD platform will provide a unique registration token. This token ensures that the runner is associated with the correct organization or repository.
- Navigate to your platform's "Runner" or "Agent" settings.
- Generate a new registration token.
- Download the agent binary or container image provided by the platform.
- Execute the registration command (e.g.,
./config.sh --url <url> --token <token>).
Step 3: Configuring the Runner
During the registration process, you will be prompted to provide a name for the runner and assign it "tags." Tags are essential for routing specific jobs to specific runners. For example, if you have a runner with a GPU, you might tag it as gpu-enabled.
Step 4: Running as a Service
You should never run a production runner as a temporary shell process. It must be configured as a system service so that it starts automatically on boot and restarts if the process crashes.
# Example: Installing as a systemd service on Linux
sudo ./svc.sh install
sudo ./svc.sh start
YAML Pipeline Configuration and Targeting
Once your runner is active, you need to tell your YAML pipeline to use it. In most systems, this is done using the runs-on or tags property.
Example: Targeting a Specific Runner
jobs:
build-and-test:
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run build
run: ./build-script.sh
If you have multiple runners, you can use tags to ensure your job lands on the right machine.
jobs:
gpu-task:
runs-on: [self-hosted, gpu-enabled]
steps:
- name: Train Model
run: python train.py
Callout: The Power of Labels and Tags Labels act as a filter. When you define
runs-on: [self-hosted, linux, high-memory], the platform engine looks for a runner that possesses all three of those tags. This allows you to build a sophisticated "fleet" of runners, each specialized for different tasks, and let the platform handle the load balancing between them.
Best Practices for Maintaining Runners
Managing your own runners is an operational commitment. If your runners go down, your development velocity drops to zero. Follow these best practices to keep your pipeline infrastructure reliable.
1. Automation is Mandatory
Never manually configure a runner. Use configuration management tools like Ansible, Terraform, or even simple cloud-init scripts to provision your runner hosts. If a runner fails, you should be able to spin up a replacement in minutes by running a single command or pipeline.
2. Ephemeral Runners (The Gold Standard)
Whenever possible, configure your runners to be "ephemeral." This means the runner handles exactly one job and then shuts itself down. This prevents "configuration drift," where a runner becomes dirty over time due to leftover files, stale caches, or modified environment variables. Many platforms now support a "one-job-per-runner" mode which is highly recommended for security and stability.
3. Monitoring and Alerting
Your runners should be treated as production infrastructure. Monitor the following metrics:
- CPU and Memory Usage: High utilization can lead to slow builds and potential crashes.
- Disk Space: Build directories and caches can fill up partitions rapidly.
- Connection Status: Ensure the runner is successfully communicating with the platform API.
- Job Queue Depth: If jobs are waiting too long, you need to scale up your runner count.
4. Security Isolation
A self-hosted runner often has access to your source code and potentially your deployment credentials. If a malicious actor manages to commit code that executes a command on your runner, they could theoretically compromise your entire internal network. Run your runners in isolated virtual machines or, better yet, use containerized runners where each job runs in a clean, disposable container.
Comparison of Runner Strategies
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Shared Managed | No maintenance, easy setup | Limited control, security concerns | Small teams, generic projects |
| Persistent Self-Hosted | High performance, network access | High maintenance, configuration drift | Legacy builds, specialized hardware |
| Ephemeral Self-Hosted | Clean environment, high security | Requires infrastructure automation | Large teams, complex CI/CD needs |
Common Pitfalls and How to Avoid Them
Pitfall 1: "Dirty" Runners
When a runner is persistent, it holds onto files from previous jobs. If a developer assumes the workspace is clean, they might write code that fails only when run on a fresh machine.
- Fix: Always include a "cleanup" step in your YAML or use ephemeral runner configurations that destroy the workspace after every job.
Pitfall 2: Environment Variable Leakage
It is tempting to store secrets directly on the runner host. This is a security disaster.
- Fix: Always use the platform's secret management system. Even on a self-hosted runner, secrets should be injected into the environment by the pipeline runner at runtime, not hardcoded into the system's global profile.
Pitfall 3: Version Mismatch
If your pipeline uses a specific version of a build tool (e.g., Maven 3.8.1) but your runner has a different version (3.6.3), your builds will behave inconsistently.
- Fix: Use containerized builds. By defining a container image in your YAML pipeline, you ensure that the tools inside the container are identical regardless of the underlying runner host.
# Example: Using a container for consistent builds
jobs:
build:
runs-on: self-hosted
container:
image: maven:3.8.1-openjdk-11
steps:
- run: mvn clean install
Pitfall 4: Ignoring Network Latency
If your runner is in a data center in London but your container registry is in a US-East region, your build times will be dominated by network latency.
- Fix: Place your runners in the same region as your primary cloud resources (e.g., your artifact repository and your deployment targets).
Scaling Your Runner Fleet
As your organization grows, a single runner will become a bottleneck. You need to transition from "a runner" to a "runner fleet."
Horizontal Scaling
Horizontal scaling involves adding more runner machines to your pool. Most modern CI/CD platforms allow you to define "Runner Groups." You can add multiple runners to a single group, and the platform will automatically distribute jobs across them.
Autoscaling
The most advanced implementation is autoscaling. Instead of having a fixed number of servers running 24/7, you use an autoscaler (often provided as an add-on or a community project) that monitors the job queue.
- When the queue grows, the autoscaler triggers an API call to your cloud provider (e.g., AWS EC2 or Kubernetes) to spin up new runner instances.
- Once the jobs are completed and the queue is empty, the autoscaler terminates the idle instances to save costs.
This approach combines the control of self-hosted runners with the convenience and cost-efficiency of managed cloud infrastructure. It requires more initial setup but is the gold standard for enterprise-grade pipeline architecture.
Security Considerations for Self-Hosted Infrastructure
When you move the runner inside your network, you are effectively extending your network's attack surface. You must treat these machines with the same security rigor as your production application servers.
Network Egress Control
A common mistake is giving the runner unrestricted access to the internet. If a build script is compromised, it could reach out to a malicious server to download secondary payloads or exfiltrate sensitive data. Use network policies or firewalls to restrict the runner's egress traffic to only the domains it truly needs (e.g., your package registry, your container registry, and the CI/CD platform's API).
Identity and Access Management (IAM)
If your runners are hosted on a cloud provider like AWS or GCP, do not store static access keys on the runner. Instead, use the cloud provider's native identity features (such as IAM Roles for Service Accounts in Kubernetes). This allows the runner to authenticate to cloud services using temporary, short-lived tokens, which significantly reduces the risk of credential theft.
Regular Patching
Self-hosted runners are often forgotten servers. If you are running a Linux-based runner, ensure it is part of your organization's patch management cycle. A vulnerability in an unpatched kernel on a runner could be the entry point for an attacker to move laterally into your internal network.
Troubleshooting Common Issues
Even with careful planning, things will go wrong. Here is how to handle the most common issues you will face.
The "Runner is Offline" Error
This is the most common issue. It usually means the connection between the runner process and the CI/CD platform has been severed.
- Check the logs: The runner software usually logs to a file in its installation directory. Look for "connection reset" or "authentication error" messages.
- Check network connectivity: Ensure the machine can reach the platform's API endpoint. Use
curlorpingto verify connectivity. - Check the token: Registration tokens can expire. If the runner was not registered recently, you may need to re-register it.
The "Job Stuck in Pending" Error
If your jobs remain in a "pending" or "queued" state, it means the platform cannot find a runner that matches the requirements.
- Check Tags: Ensure the tags on your YAML job match the tags assigned to your runners exactly. Case sensitivity often causes issues here.
- Check Runner Capacity: If all your runners are busy, the job will stay in the queue. You may need to scale your fleet.
- Check Platform Health: Sometimes the platform itself has an outage. Check the status page of your CI/CD provider.
The "Permission Denied" Error
This usually happens when the user account running the agent process does not have permission to execute the commands in your pipeline.
- The Rule: The runner should run as a non-privileged user whenever possible. However, if your build requires installing global packages or accessing specific system files, you might need to grant the runner user specific
sudopermissions or adjust file ownership.
Summary and Key Takeaways
Implementing self-hosted runners is a significant step toward taking full ownership of your software delivery process. It requires a shift in mindset from "consuming a service" to "managing infrastructure." While it introduces complexity, the benefits in terms of security, performance, and flexibility are unparalleled for teams operating at scale.
Key Takeaways for Your Implementation:
- Start Small, Scale Later: Don't try to build an automated autoscaling fleet on day one. Start with a single, manually configured runner to understand the mechanics, then move to automation.
- Prioritize Ephemeral Runners: Always aim for a "one-job-per-runner" model. It is the single most effective way to prevent configuration drift and security vulnerabilities.
- Use Containers for Consistency: Decouple your build environment from the host operating system by using containerized build steps. This makes your pipelines portable and predictable.
- Treat Runners as Production Infrastructure: Your runners are as important as your application servers. Monitor them, patch them, and automate their deployment.
- Security is Non-Negotiable: Because runners operate inside your network, they are high-value targets. Restrict their network access and use short-lived credentials.
- Master the Tags: Use labels and tags to intelligently route jobs to the right resources. This is the key to maintaining a diverse fleet of runners for different project needs.
- Document the "How": Because self-hosted runners involve custom configuration, ensure you have internal documentation on how to replace a runner, how to update the agent software, and how to troubleshoot common issues.
By following these principles, you will build a robust, efficient, and secure pipeline infrastructure that can grow alongside your organization's needs. Remember that the goal is not just to get the code built, but to create a reliable and repeatable process that empowers your developers to ship with confidence.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons