Understanding CI/CD Runners
What Is a Runner in Simple Terms
A runner is the worker that actually runs your pipeline jobs. When GitHub Actions starts a job, it needs somewhere to execute the commands — that somewhere is a runner. Think of runners as the factory workers on your assembly line: the pipeline is the process, the runner is the person (or machine) doing the work.
GitHub-hosted runners are virtual machines that GitHub spins up for each job and terminates when the job completes. Self-hosted runners are machines you manage — physical servers, VMs, or Kubernetes pods — that stay registered with your CI platform and pick up jobs from a queue.
How It Works
+------------------------------------------+| GitHub Actions Workflow || job: build || runs-on: ubuntu-latest |+------------------------------------------+ | job queued | v+------------------------------------------+| GitHub picks an available runner || || GitHub-hosted: || Fresh Ubuntu VM, 2 vCPU, 7GB RAM || Pre-installed: Node, Python, Docker || Cost: billed per minute || Terminated after job completes |+------------------------------------------+ OR+------------------------------------------+| Self-hosted: || Your server/VM/Kubernetes pod || Custom tools, faster network, || access to private resources || Cost: your infrastructure |+------------------------------------------+Runner selection by label:
jobs: build: ## GitHub-hosted runner runs-on: ubuntu-latest deploy-prod: ## Self-hosted runner with specific label ## Only runs on your runner in the production VPC runs-on: [self-hosted, production, linux] build-arm: ## GitHub-hosted ARM runner runs-on: ubuntu-latest strategy: matrix: platform: [linux/amd64, linux/arm64]Self-hosted runner setup:
## Download runner packagemkdir actions-runner && cd actions-runnercurl -o actions-runner-linux-x64-2.311.0.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gztar xzf ./actions-runner-linux-x64-2.311.0.tar.gz ## Configure runner (get token from GitHub repo settings)./config.sh \ --url https://github.com/razorpay/payment-api \ --token RUNNER_TOKEN \ --labels production,linux,payment-vpc ## Install as a servicesudo ./svc.sh installsudo ./svc.sh start ## GitLab runner registrationgitlab-runner register \ --url https://gitlab.com \ --token REGISTRATION_TOKEN \ --executor docker \ --docker-image alpine:latest \ --description payment-api-runnerPractical Commands
## GitHub CLI -- list runners for a repositorygh api repos/OWNER/REPO/actions/runners ## List runner groupsgh api orgs/ORG/actions/runner-groups ## GitLab -- check runner statusgitlab-runner statusgitlab-runner list ## Jenkins -- check node statusjava -jar jenkins-cli.jar -s http://jenkins.internal list-nodesTroubleshooting
| Symptom | Check | What to Look For |
|---|---|---|
| Job queued but not starting | Runner availability | All runners busy or offline |
| Self-hosted runner offline | Runner service status | ./svc.sh status on runner machine |
| Tool not found on runner | Runner image/labels | Use correct runner label for required tools |
| Runner picking wrong jobs | Label configuration | Labels must match exactly |
COMMON MISTAKE / WARNING**Security:** Never use self-hosted runners on public repositories unless you have implemented strict security controls. A malicious PR can execute arbitrary code on your self-hosted runner — which may have access to production credentials and infrastructure. Use GitHub-hosted runners for public repos and self-hosted only for private repos with restricted access.
REMEMBER THIS**Remember:** GitHub-hosted runners start fresh every time — nothing persists between jobs. If Job A installs a tool, Job B does not have it. Use setup actions (actions/setup-node, actions/setup-python) at the start of each job, or build a custom runner image with your tools pre-installed.
COMMON MISTAKE / WARNING**Common Mistake:** Installing tools directly on a self-hosted runner instead of using Docker containers or setup actions. A runner that has been manually configured over time becomes a snowflake — impossible to reproduce, impossible to scale horizontally, and a nightmare when the machine needs to be replaced. Keep runners stateless and install tools per-job using setup actions or Docker.
PLACEMENT PRO TIP**Tip:** Use runner labels to target specific hardware for specific jobs. A Docker build job that benefits from a high-core-count machine can use `runs-on: [self-hosted, high-cpu]`. An ARM image build runs on `runs-on: [self-hosted, arm64]`. Label your runners by capability, not by machine name, so you can scale horizontally.