Install and Configure a GitLab Runner
Install GitLab Runner on Linux, register Shell and Docker executors, configure caches, route jobs with tags, and understand autoscaling with the fleeting plugin.
Before you start
- ▸A running GitLab instance (GitLab.com or self-managed 16+) with permission to create runners
- ▸A Linux server with at least 2 GB RAM and outbound HTTPS access to the GitLab host
- ▸sudo or root access on the runner host
- ▸Docker Engine installed and running (required only for the Docker executor steps)
A GitLab Runner is the agent that picks up CI/CD jobs from your GitLab instance and executes them. The runner itself is a single Go binary; the executor you choose determines how each job runs — directly on the host shell, inside a fresh Docker container, or on ephemeral cloud VMs. This guide walks through installing a runner, registering it with both the Shell and Docker executors, tuning caches, adding job tags, and sketching out autoscaling so you can choose the right level of complexity for your workload.
Install the GitLab Runner Package
GitLab maintains its own package repository. Do not use your distro's packaged version — it lags behind and often ships an incompatible older release.
Debian / Ubuntu
curl -fsSL https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt install gitlab-runner
Fedora / RHEL / Rocky
curl -fsSL https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo dnf install gitlab-runner
Arch Linux
sudo pacman -S gitlab-runner
The package creates a gitlab-runner system user, installs the binary to /usr/bin/gitlab-runner, and registers a systemd service. Verify the install:
gitlab-runner --version
Obtain a Registration Token
Tokens scope to three levels. Use the most specific one that fits your use case.
- Project-level: Settings → CI/CD → Runners → Project runners → New project runner
- Group-level: Group → Settings → CI/CD → Runners → New group runner
- Instance-level (self-managed GitLab only): Admin → CI/CD → Runners → New instance runner
GitLab 16+ uses the new runner authentication token flow (glrt- prefix). The old REGISTRATION_TOKEN approach is deprecated and disabled in GitLab 18. This guide uses the new flow.
Register a Shell Executor
The shell executor runs jobs directly as the gitlab-runner user on the host. Use it when you need native access to hardware, specific compilers, or host GPU resources. Avoid it for untrusted code — a malicious job can read anything the runner user can read.
sudo gitlab-runner register \
--url https://gitlab.example.com \
--token glrt-YOURTOKEN \
--executor shell \
--description "shell-runner-prod" \
--tag-list "shell,linux,build"
The interactive prompts are skipped when you pass all flags on the command line. The configuration is written to /etc/gitlab-runner/config.toml.
Register a Docker Executor
The Docker executor spins up a fresh container per job, giving strong isolation and a clean environment every run. Install Docker Engine first if it is not present.
Install Docker Engine
# Debian/Ubuntu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker gitlab-runner
# Fedora/RHEL/Rocky
sudo dnf install docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker gitlab-runner
sudo systemctl enable --now docker
Register the Runner
sudo gitlab-runner register \
--url https://gitlab.example.com \
--token glrt-YOURTOKEN \
--executor docker \
--docker-image "alpine:latest" \
--description "docker-runner-prod" \
--tag-list "docker,linux" \
--docker-volumes "/cache:/cache"
--docker-image sets the fallback image used when a job's .gitlab-ci.yml does not specify one. --docker-volumes mounts a host path into every container — essential for the cache layer covered next.
Configure Caching
Without caching, every job re-downloads dependencies. Two main options exist: a local directory cache or a distributed cache via S3-compatible object storage.
Local Cache (single runner host)
Edit /etc/gitlab-runner/config.toml. Locate the [[runners]] block for your Docker runner and add:
sudo nano /etc/gitlab-runner/config.toml
[runners.cache]
Type = "local"
Path = "/cache"
The /cache path must match the volume you mounted with --docker-volumes.
Distributed Cache with S3 / MinIO
For multiple runners or autoscaling, every runner must hit the same cache backend. MinIO is a popular self-hosted option.
[runners.cache]
Type = "s3"
Shared = true
[runners.cache.s3]
ServerAddress = "minio.example.com:9000"
AccessKey = "AKIAIOSFODNN7EXAMPLE"
SecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
BucketName = "gitlab-runner-cache"
Insecure = false
Set Insecure = true only in internal non-TLS lab environments. In .gitlab-ci.yml, declare what to cache:
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
- .m2/repository/
Using Tags to Route Jobs
Tags let you target specific runners from your pipeline. A job only runs on a runner that has all of the job's requested tags.
build-arm:
tags:
- docker
- arm64
script:
- make build
Add or change tags without re-registering by editing config.toml directly:
[[runners]]
name = "docker-runner-prod"
tags = ["docker", "linux", "arm64"]
Then reload the runner — no restart required for config changes:
sudo gitlab-runner reload
Runners with run_untagged = true pick up jobs that declare no tags at all. Set this to false on specialized runners to prevent accidental job misrouting.
Autoscaling Basics
Autoscaling creates ephemeral VMs on demand and tears them down after each job. The two main approaches in 2024 are the legacy Docker Machine executor (still functional but unmaintained) and the newer GitLab Runner Autoscaler built on fleeting plugins.
Fleeting-based Autoscaler (recommended)
Install the AWS or GCP fleeting plugin alongside the runner binary. Then configure config.toml:
[[runners]]
name = "autoscale-docker"
executor = "docker-autoscaler"
[runners.autoscaler]
plugin = "fleeting-plugin-aws"
capacity_per_instance = 1
max_instances = 10
min_idle_count = 0
max_idle_count = 2
idle_time = "20m0s"
[runners.autoscaler.plugin_config]
name = "gitlab-runner-asg"
region = "us-east-1"
[runners.docker]
image = "alpine:latest"
max_instances is your hard ceiling. idle_time keeps a warmed instance alive briefly to absorb burst traffic before scaling to zero. Always set min_idle_count = 0 in cost-sensitive environments.
Manage the Service
sudo systemctl enable --now gitlab-runner
sudo systemctl status gitlab-runner
sudo systemctl restart gitlab-runner
Verify the Runner is Online
From the GitLab UI, navigate to the runner's configuration page. A green circle indicates the runner contacted GitLab within the last 2 minutes. You can also trigger a test pipeline or run a one-shot job from the host:
sudo gitlab-runner run-single \
--url https://gitlab.example.com \
--token glrt-YOURTOKEN \
--executor shell \
--max-builds 1
Troubleshooting
Runner shows offline immediately after registration
Check that the runner host can reach the GitLab instance on port 443 (or 80 for HTTP). Confirm the system clock is within a few seconds of GitLab's — token validation is time-sensitive.
curl -fsSL https://gitlab.example.com/api/v4/version
timedatectl status
Docker executor: permission denied on /var/run/docker.sock
The gitlab-runner user must be in the docker group. Re-login or restart the service after adding:
sudo usermod -aG docker gitlab-runner
sudo systemctl restart gitlab-runner
Jobs hang waiting for a runner
Verify the job's tags match at least one runner's tag list, and that the runner is not paused. Check concurrent job limits in config.toml:
grep -E 'concurrent|limit' /etc/gitlab-runner/config.toml
The top-level concurrent key caps total simultaneous jobs across all runners on the host. Raise it if you have spare CPU and RAM.
Frequently asked questions
- What is the difference between the Shell and Docker executors?
- The Shell executor runs jobs directly on the host OS as the gitlab-runner user, so all host tools are available but there is no isolation between jobs. The Docker executor launches a fresh container per job, providing a clean environment and preventing jobs from affecting each other or the host.
- Can one machine run multiple registered runners?
- Yes. Each registration adds a [[runners]] block to /etc/gitlab-runner/config.toml. The top-level 'concurrent' setting in that file controls the total number of jobs that run simultaneously across all runners on the host.
- How do I update the GitLab Runner binary?
- Use your package manager — apt upgrade gitlab-runner or dnf upgrade gitlab-runner. The package handles stopping and restarting the service. No re-registration is needed for minor or patch version upgrades.
- My jobs are not picking up the cache. What should I check?
- For local caches, confirm the host path in --docker-volumes matches the Path in [runners.cache]. For S3 caches, check that the BucketName exists, credentials are correct, and the cache key in .gitlab-ci.yml is consistent across jobs that should share it.
- Is the legacy Docker Machine autoscaler still viable?
- It works but is effectively unmaintained by GitLab. For new deployments, use the fleeting-based docker-autoscaler executor introduced in GitLab Runner 15.11+. Docker Machine autoscaling will eventually be removed.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.