Learn what a GitLab Runner is and how it runs jobs: shared versus specific runners, executors, installing and registering on Ubuntu, and a Docker executor.
A GitLab Runner is the worker process that actually executes the jobs you define in .gitlab-ci.yml. GitLab itself stores your repository, reads your pipeline definition, calculates which jobs should run, and shows the results in the UI. The runner is the agent that picks up one of those jobs, prepares an execution environment, runs the commands, collects logs, uploads artifacts, and reports the status back to GitLab. If GitLab CI/CD is the brain of your automation system, the runner is the pair of hands doing the work.
This distinction matters because many GitLab beginners assume that adding a job to .gitlab-ci.yml is enough by itself. In reality, GitLab needs available runners. Without a runner, jobs remain stuck in a pending state. That is why understanding GitLab CI runner architecture, GitLab runner types, and GitLab runner registration is essential for building reliable pipelines.
GitLab pipelines are declarative. You describe stages, jobs, rules, images, artifacts, environments, and deployment behavior in YAML. But YAML does not execute itself. GitLab therefore relies on the GitLab Runner application, which can be installed on Linux, Windows, macOS, virtual machines, cloud hosts, or Kubernetes nodes.
A runner exists for a few practical reasons:
When a pipeline starts, GitLab places jobs into a queue. Runners poll GitLab for matching jobs. A runner only picks up a job if the job matches its scope and tags, and if the runner is allowed to execute it. Once assigned, the runner prepares the job environment, checks out the repository, restores cache if configured, runs the job script, uploads artifacts, and returns either success or failure.
When people search for shared runner GitLab, group runner, or specific runner GitLab, they are really asking who a runner belongs to and which projects may use it.
Shared runners are available to many projects. On GitLab.com, shared runners are the easiest way to get started because GitLab hosts and manages them for you. You do not need to install anything. If your project is allowed to use shared runners, your jobs can start immediately.
Shared runners are convenient for:
However, shared runners also come with tradeoffs:
That is why many companies start with shared runners for development workloads and move critical deployment jobs to self-hosted runners.
Group runners are attached at the GitLab group level and can be used by projects within that group. This is a good middle ground between convenience and control.
Use group runners when:
Group runners are especially helpful in monorepos or in organizations with many microservices using the same language stack.
A project-specific runner is registered for one project only. This is often the safest and most controlled option.
Use project runners when:
If your project deploys to production or touches highly sensitive infrastructure, a project-specific runner is often a better design than relying on shared runners.
The next major concept is the GitLab runner executor. The executor determines how the job runs.
The shell executor runs job commands directly on the host machine using Bash, PowerShell, or another supported shell.
The shell executor is fine for trusted internal jobs, but it is usually not the best default for multi-tenant CI.
The Docker executor is the most popular option. Each job runs inside a container created from the configured image. That gives you clean, repeatable environments and makes language-specific builds straightforward.
The docker+machine executor creates temporary Docker hosts on demand, traditionally using Docker Machine. Conceptually, this gives you elastic runners that spin up new machines for jobs and tear them down later.
In modern GitLab environments, many teams now prefer Kubernetes-based autoscaling rather than older Docker Machine workflows, but the concept is still worth knowing.
The Kubernetes executor launches each CI job as a pod in a Kubernetes cluster.
The SSH executor connects to an external machine over SSH and runs commands there.
Because the GitLab runner Docker executor is so widely used, it deserves a closer look. When a job is assigned to a Docker executor runner, the runner performs a sequence like this:
That “fresh container per job” model is the reason Docker executor pipelines are so predictable. If one job installs a package or writes files, those changes do not magically leak into the next job unless you explicitly store them as artifacts or cache. This clean-slate behavior reduces the “works on one runner, fails on another” problem.
For example, if your job uses:
The Docker executor pulls node:20, starts a container, and runs the Node commands there. Another job in the same pipeline could use python:3.12 with no conflict because it gets a separate container.
image: node:20
script:
- npm ci
- npm testIf you want your own runner, installing GitLab Runner on Ubuntu is a common choice. The exact package source may vary by GitLab version, but the typical flow looks like this.
If you plan to use the Docker executor, also install Docker on the host and ensure the runner can access the Docker daemon.
Depending on your environment, you may need to restart the service after group membership changes.
sudo apt-get update
sudo apt-get install -y curl ca-certificatesInstalling the binary is not enough. The runner must be registered with a GitLab instance so GitLab knows which runner belongs to which scope.
A standard interactive registration looks like this:
You are typically prompted for:
An example interactive session might look like:
You can also do non-interactive registration in automation scripts:
This is useful when provisioning runners with Infrastructure as Code.
sudo gitlab-runner registerAfter registration, GitLab Runner stores its configuration in config.toml, commonly located at:
A simplified example looks like this:
Important fields include:
In real environments, config.toml can contain additional settings for cache backends, Kubernetes namespaces, session servers, custom volumes, pull policies, and network settings.
/etc/gitlab-runner/config.tomlGitLab runner tags are one of the most practical controls in CI/CD. Tags let you ensure the right jobs go to the right runners.
Suppose you have two runners:
Then you can route a job like this:
And a production deployment job like this:
Tags are critical when you have specialized runners for:
Without tags, jobs may land on any eligible runner, which can cause failures or security problems.
build-image:
stage: build
tags:
- docker
script:
- docker build -t myapp .Security is where many teams underestimate the impact of runner design. A runner can see repository code, job logs, variables, artifacts, and possibly deployment credentials. Treat runners as trusted infrastructure, not disposable trivia.
Shared runners are convenient, but they are not ideal for highly sensitive production credentials. Even when GitLab isolates jobs, a broad shared environment increases your risk surface. For production deployments, it is safer to use self-hosted runners that you control, ideally scoped to one group or one project and protected by tags.
A protected runner only executes jobs from protected branches or protected tags. This prevents an untrusted feature branch from using a runner that has access to sensitive environments.
A protected variable is only exposed to jobs running on protected refs. This pairs naturally with protected runners.
That combination is far safer than placing production credentials in jobs that can run on any shared runner.
The concurrent setting in config.toml controls how many jobs the runner process may execute simultaneously. If concurrent = 4, up to four jobs can run at the same time, subject to machine resources and runner configuration.
Concurrency is a balancing act:
On a small VM, concurrent = 1 or 2 may be appropriate. On a larger build host with plenty of CPU and memory, higher values might be fine. Monitor actual job performance before increasing it aggressively.
Here is a practical end-to-end example.
This is the core of how GitLab CI runner, GitLab runner Docker executor, and GitLab runner tags work together.
sudo gitlab-runner register --non-interactive --url "https://gitlab.com/" --token "glrt-xxxxxxxxxxxxxxxx" --description "team-docker-runner" --tag-list "docker,team-a" --executor "docker" --docker-image "node:20"A few common problems cause runner confusion:
A GitLab Runner is the engine that turns CI/CD configuration into actual execution. Once you understand runner scope, executor types, tags, registration, and security boundaries, your pipelines become much easier to design and troubleshoot. For most teams, the Docker executor is the best starting point because it offers clean isolation, reproducible jobs, and excellent compatibility with modern CI workflows. As your platform grows, group runners, protected runners, and carefully chosen concurrency settings help you scale without losing control.
What is the primary job of a GitLab Runner in GitLab CI/CD?
Why is the Docker executor a popular default for GitLab CI runners?
Which setup is safest for production deployment jobs that use sensitive credentials?
Control exactly when GitLab CI jobs run with rules:if, rules:changes and rules:exists, plus workflow rules, merge request pipelines and manual jobs.
Track deployments with GitLab CI environments: the environment keyword, static versus dynamic environments, review apps, protected environments and approvals.
Build and push Docker images in GitLab CI: compare Docker-in-Docker with Kaniko, authenticate to the Container Registry, pick tags and follow a full example.