Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

By updated

Improve this page

GitLab logo

You now know how to write a .gitlab-ci.yml file with stages, jobs and scripts. But who actually runs these jobs? The runners, agents that pick up your jobs and run the commands. This guide explains how they work and how to optimize them.

What you will learn

By the end of this module, you will be able to:

  • Understand the role of a runner: the agent that runs your jobs
  • Tell shared, group and project runners apart: choose the right type for your needs
  • Use tags: target a specific runner for a job
  • Choose an executor: Docker, Shell or Kubernetes depending on your context
  • Diagnose a "pending" job: identify why a job does not start
  • Optimize performance: reduce execution times

Prerequisites

Before continuing, make sure you are comfortable with:

What is a runner?

A runner is an agent that:

  1. Fetches pending jobs from GitLab
  2. Runs the job's commands
  3. Sends the results back (logs, artifacts, status)
The GitLab server holds the pending jobs; the runner, an agent, fetches them and runs their commands; the job, a script, builds, tests and deploys, then sends its result back to the runner, which reports it to GitLab.GitLab (server)pending jobsRunner (agent)runs the commandsJob (script)builds, tests, deploys
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

What types of runners exist?

TypeScopeUse case
Shared runnersAll GitLab.com projectsStandard projects, no setup
Group runnersProjects of a groupTeams with common needs
Project runnersA single projectSecurity, performance, specific needs

Shared runners

GitLab.com provides shared runners, free of charge (with quotas). They are:

  • Preconfigured with Docker
  • Available immediately
  • Limited in minutes per month (depending on your plan)

To use them, there is nothing to configure: your jobs run on them automatically.

Runners available in GitLab - Settings > CI/CD > Runners

In Settings > CI/CD > Runners, you see the runners available to your project: their status (online/offline), their tags, and whether they are shared or dedicated.

Group runners

Group runners are shared among all the projects of a GitLab group. They are a good compromise between shared runners (too generic) and project runners (too isolated).

Use cases:

  • Teams with several projects sharing the same needs (common tech stack)
  • Centralized governance: a group admin manages the runners for all projects
  • Control of tags and access at the team level

To create a group runner: Group > Settings > CI/CD > Runners > New group runner. The group's projects use it automatically (unless disabled at the project level).

Project runners (self-hosted)

Install your own runner for:

  • Performance: dedicated machine, no queue
  • Security: jobs that access internal resources
  • Specialization: GPU, ARM, specific tools
  • Cost: avoid minute quotas

How do you follow job execution?

When a pipeline runs, you can follow its progress in Build > Pipelines:

List of GitLab pipelines

Each row represents a pipeline with its status, its branch and its trigger. Click a pipeline to see the detail of the stages and jobs:

Graph of a GitLab pipeline - view of stages and jobs

This graph shows the stages (columns) and the jobs (boxes). The jobs of a same stage run in parallel. Click a job to see its logs in real time:

Logs of a GitLab CI job while it runs

The logs show each command executed and its output. You see the CI variables (such as $CI_COMMIT_BRANCH) resolved with their actual values.

Executors

The executor defines how the runner runs the jobs:

ExecutorDescriptionUse case
DockerEach job in an isolated containerStandard, isolation, reproducibility
Docker+MachineCreates VMs on demand (autoscaling)Variable load, cloud
ShellRuns directly on the machineHardware access, simplicity
KubernetesEphemeral Kubernetes podsExisting K8s cluster
SSHConnects to a remote machineDeployment to existing servers

Docker executor

The most common. Each job runs in a Docker container.

job:
image: node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd # Docker image used
script:
- npm install
- npm test

Advantages:

  • Complete isolation between jobs
  • Reproducible environment
  • Wide choice of available images

Drawbacks:

  • Image pull time
  • No direct hardware access

Shell executor

Runs the commands directly on the runner's machine.

job:
tags:
- shell # Targets a runner with the shell executor
script:
- ./build.sh

Advantages:

  • Direct hardware access (GPU, USB...)
  • No Docker overhead
  • Preinstalled tools available

Drawbacks:

  • No isolation between jobs
  • Dependencies must be installed on the machine

How do tags work?

Tags let you target a specific runner. A job with tags only runs on a runner that has all of those tags.

build:
tags:
- docker
- linux
script:
- make build
build-arm:
tags:
- docker
- arm64
script:
- make build

On the runner side, tags are defined at registration:

Terminal window
gitlab-runner register \
--tag-list "docker,linux,amd64"

How do you install a runner?

  1. Download the binary and check its checksum

    Pin the version rather than downloading latest, which changes without notice. The runner version should match your GitLab version (here 19.5.1, the version this course is measured on). GitLab publishes a release.sha256 file next to each version, and sha256sum -c compares the download with it:

    Terminal window
    RUNNER_VERSION="19.5.1" # align with your GitLab version
    base="https://gitlab-runner-downloads.s3.amazonaws.com/v${RUNNER_VERSION}"
    curl -fsSLO "${base}/binaries/gitlab-runner-linux-amd64"
    curl -fsSL "${base}/release.sha256" \
    | grep 'binaries/gitlab-runner-linux-amd64$' \
    | sed 's#binaries/##' \
    | sha256sum -c -

    The check must print gitlab-runner-linux-amd64: OK. Any other output means the file is corrupted or altered: delete it and start again. Then install it, with sudo because /usr/local/bin belongs to root:

    Terminal window
    sudo install -m 0755 gitlab-runner-linux-amd64 /usr/local/bin/gitlab-runner
    gitlab-runner --version
  2. Create a dedicated user

    Terminal window
    sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
  3. Install and start the service

    Terminal window
    sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
    sudo gitlab-runner start
  4. Create the runner in GitLab

    In Settings > CI/CD > Runners, click New project runner. Set the tags and options, then GitLab generates a runner authentication token (prefix glrt-).

  5. Register the runner

    Use the glrt-... token with the --token option:

    Terminal window
    sudo gitlab-runner register \
    --url "https://gitlab.com/" \
    --token "glrt-XXXXXXXXXXXXXXXX" \
    --name "my-docker-runner" \
    --executor "docker" \
    --docker-image "alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b" \
    --tag-list "docker,linux"

    --docker-image is the default image of jobs that do not declare their own image:. As in a .gitlab-ci.yml, it is pinned by its digest (@sha256:...), so every job starts from the exact same content.

How do you configure the runner?

The configuration file is /etc/gitlab-runner/config.toml:

concurrent = 4 # Max simultaneous jobs on this runner
check_interval = 3 # GitLab polling interval (in seconds)
[[runners]]
name = "my-runner"
url = "https://gitlab.com/"
token = "glrt-xxxx"
executor = "docker"
[runners.docker]
image = "alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b"
privileged = false
volumes = ["/cache"]
pull_policy = ["always", "if-not-present"] # Fallback if the registry is unavailable

Important parameters:

ParameterDescription
concurrentNumber of simultaneous jobs on this runner
check_intervalInterval between polls to GitLab (spread across the runners in the file)
pull_policyPull strategy: always, if-not-present, never (or a list for fallback)
privilegedPrivileged mode (required for Docker-in-Docker, security risk)
volumesVolumes mounted in the containers

How do you diagnose a pending job?

Is your job stuck on "pending"? Here are the possible causes:

SymptomLikely causeSolution
"Waiting for a runner"No runner availableCheck that the runners are active
"This job is stuck"Tags do not matchCheck the tags of the job and of the runner
Pending for a long timeAll runners busyIncrease concurrent or add runners
Pending then failRunner offlineCheck the gitlab-runner service

Diagnosis:

Terminal window
# Check the runner status
gitlab-runner status
# See the registered runners
gitlab-runner list
# Test the connection to GitLab
gitlab-runner verify

In GitLab: Settings > CI/CD > Runners shows the available runners and their status (see the screenshot above).

Why are my jobs slow?

CauseImpactSolution
Image pull on every job+30s to +2minpull_policy: if-not-present
npm install on every job+1 to 5minUse the cache
Overloaded shared runnersQueueDedicated runner
Heavy imageDownload timeLighter or custom image

What are the best practices?

  1. Use meaningful tags: docker-linux, gpu, prod-deploy
  2. Limit production runners: dedicated tags, restricted access
  3. Configure concurrent correctly: according to the machine's resources
  4. Use pull_policy: if-not-present on a private runner: avoids unnecessary pulls (keep always on shared runners, see the caution above)
  5. Monitor your runners: dashboards, alerts if offline

Hands-on lab

The lab "The right image for each job" replays this guide on a GitLab CE forge of your own, set up on a virtual machine with its runner and its registry, rather than on the shared runners of gitlab.com: no minute quota, no identity verification, and a project you can break as often as you need.

Three jobs share the same heavy Python image, and the one that builds the image runs docker build from it, which cannot work. You pick an image that matches what each job does, and give the build job a Docker daemon (building an image is detailed in GitLab registries). Grading is taken on the job logs, never on the file.

The lab starts with dsoxlab run gl-03-images-et-runners, after dsoxlab provision prepares the virtual machine, its runner and its registry. Installing DSOXLab and its prerequisites are described in the DSOXLab documentation.

Key takeaways

  1. Runner = agent that runs the jobs on a machine
  2. Shared runners are free (with quotas), project runners give full control
  3. Docker executor recommended for isolation and reproducibility
  4. Tags = target a specific runner (tags: [docker, gpu])
  5. Pending job = check tags, active runners, and concurrent
  6. Performance = cache, pull_policy, dedicated runners

Knowledge check

Test your knowledge of GitLab CI/CD runners.

Knowledge check

Check what you have learned with this interactive quiz

10 questions
5 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn