What are GitLab Runners and how do they execute CI/CD jobs?
Quick Answer
GitLab Runners are lightweight agents that pick up and execute CI/CD jobs defined in .gitlab-ci.yml. They can be shared across all projects, assigned to a specific group, or dedicated to a single project. Runners support multiple executors including Docker, Shell, Kubernetes, and Docker Machine, determining the environment in which jobs run.
Detailed Answer
Think of GitLab Runners like delivery drivers for a food delivery platform. The platform (GitLab server) receives orders (pipeline jobs) from restaurants (repositories). Delivery drivers (Runners) are registered with the platform, check for new orders that match their delivery zone (tags), pick up an order, deliver it (execute the script), and report back whether the delivery was successful. Some drivers work for a single restaurant chain (project-specific Runners), some cover an entire neighborhood (group Runners), and some accept deliveries across the whole city (shared Runners).
A GitLab Runner is an application written in Go that you install on a machine (physical server, virtual machine, or container). After installation, you register the Runner with a GitLab instance by providing the instance URL and a registration token. During registration, you choose an executor type that determines how jobs are run: the Docker executor spins up a fresh container for each job using the image specified in .gitlab-ci.yml, the Shell executor runs commands directly on the Runner's host machine, the Kubernetes executor creates pods in a Kubernetes cluster for each job, and the Docker Machine executor auto-scales by provisioning cloud VMs on demand. You also assign tags during registration, which are labels that determine which jobs the Runner can pick up. A Runner tagged with docker and linux will only execute jobs that specify those tags.
Internally, the Runner continuously polls the GitLab server's API (by default every three seconds) asking for available jobs. When it receives a job, it downloads the job specification including the script commands, variables, and artifact configuration. For the Docker executor, it pulls the specified Docker image, creates a container, mounts the cloned repository into the container, and executes the before_script, script, and after_script commands inside it. The Runner streams log output back to the GitLab server in real time via the API, allowing developers to watch job execution live in the web interface. When the script finishes, the Runner reports the exit code (0 for success, non-zero for failure), uploads any artifacts, and destroys the container. The Runner then immediately polls for the next available job.
In production, Runner architecture is a critical infrastructure decision. A typical enterprise setup uses shared Runners on a Kubernetes cluster for standard workloads (building code, running tests, deploying to staging), with auto-scaling enabled to handle peak demand during business hours and scale down overnight. Project-specific Runners on dedicated hardware handle jobs that need access to specialized resources like GPUs for ML model training or network-attached storage for large data processing. Group-level Runners serve teams that need consistent build environments, like the mobile team needing macOS Runners for iOS builds. The Runner's concurrent setting controls how many jobs it processes simultaneously, and the limit setting on the Kubernetes executor caps the number of pods to prevent cluster resource exhaustion. Monitoring Runner health through GitLab's admin panel and Prometheus metrics is essential for keeping pipelines fast and reliable.
A critical gotcha is security with shared Runners. Since shared Runners execute jobs from any project on the instance, a malicious .gitlab-ci.yml in one project could attempt to extract secrets from the Runner's host machine or interfere with other jobs running concurrently. The Docker executor mitigates this through container isolation, but the Shell executor has no isolation at all, meaning any job can access the Runner host's filesystem, environment variables, and network. Never use the Shell executor for shared Runners in a multi-tenant environment. Another common pitfall is not tagging Runners, which causes all jobs to pile onto the first available Runner regardless of whether it has the required tools, leading to job failures and wasted time.