E-NO
GitLab Runner architecture 10 Min Read

GitLab Runner Architecture Explained with Practical Examples

calendar_today Published: 2026-08-12
update Last Updated: 2026-08-14
analytics SEO Efficiency: 97%
Technical guide illustration for GitLab Runner Architecture Explained with Practical Examples.

When a job runs on GitLab, a separate process called GitLab Runner picks it up, prepares an execution environment, runs your scripts, and reports back. Understanding how that runner is built and how it moves data gives you control over stability, speed, and troubleshooting.

This guide explains GitLab Runner architecture from an operator's point of view. You will learn the core components, how control and data move during a job, and where failures typically happen. We walk through a safe path to start with the shell executor, then add practical Docker and Kubernetes examples. Finally, we cover verification steps, failure modes, recovery, and a repeatable operations checklist.

Version and Environment Inventory

Before you change anything, capture a minimal inventory so you can reproduce and debug:

  • GitLab edition and version (self-managed or SaaS)
  • GitLab Runner version and installation method
  • OS and init system for each runner host (for example, Linux with systemd)
  • Network egress to GitLab and to any container registries or artifact stores
  • Planned executor types (shell, Docker, Kubernetes)
  • Runner tokens, scope, and tags
  • Concurrency limits and expected workload volume

Commands that help you capture the above on a Linux runner host:

gitlab-runner --version

  • Show runner version:

sudo systemctl status gitlab-runner

  • Confirm service status (systemd example):

uname -a lsb_release -a (if available) or cat /etc/os-release

  • Check OS basics:

curl -I https://gitlab.example.com/ (replace with your host)

  • Verify outbound HTTPS to GitLab:

Topology to write down (text is fine):

  • Where runners live (single VM, fleet of VMs, Kubernetes cluster)
  • Whether runners are shared or project/group-specific
  • Executor isolation expectations (host shell vs containers vs pods)
  • Data storage paths for builds, cache, and artifacts

Architecture Overview: Components and Roles

At a high level, GitLab Runner continuously polls the GitLab coordinator API, claims a job that matches its tags and permissions, prepares an execution environment via an executor, runs the job scripts, uploads artifacts and cache, and reports status.

Key components and their roles:

  • Coordinator: the GitLab API endpoints that advertise jobs and accept results
  • Runner manager: the daemon that polls for work and orchestrates executors
  • Executor: the adapter that actually runs the job (shell, Docker, Kubernetes, etc.)
  • Workspace: the builds directory where job files are checked out and executed
  • Cache and artifacts: persisted outputs uploaded after the job completes
  • Tags and concurrency: scheduling knobs that govern who runs what and how many at once
ComponentRoleExample knobs
Coordinator (GitLab)Queues jobs and records resultsURL, registration and runner tokens
Runner managerPolls for jobs and orchestrates executorsconcurrent, check_interval
ExecutorRuns scripts in an environmentexecutor = shell|docker|kubernetes
WorkspaceStores repo checkout and job filesbuilds_dir, permissions
CacheSpeeds up repeated downloadsCache backend, key, paths
ArtifactsStores job outputs for later useExpiration, when, paths

Data Flow and Control Flow

Control flow for a single job:

  1. A job enters the project queue in GitLab with required tags and rules.
  2. The runner manager polls the coordinator and sees a matching job.
  3. The runner claims the job and downloads job metadata.
  4. The executor prepares the environment:
  • Shell: switches to the service user and prepares the workspace.
  • Docker: pulls the image if needed, mounts volumes, sets env.
  • Kubernetes: creates a pod with the specified image and mounts.
  1. The runner fetches the repository according to strategy (fresh, fetch, or none) and checks out the commit.
  2. The runner executes before_script, script, and after_script steps.
  3. The runner uploads artifacts and cache if configured.
  4. The runner sends job logs and final status back to GitLab.

Data flow highlights:

  • Input: job metadata, environment variables, repository data, and container images (if used)
  • Runtime: stdout/stderr streamed to job logs, temporary files in the workspace
  • Output: artifacts archive and cache content uploaded to GitLab or configured storage

Scheduling knobs:

  • Tags: a job runs only on a runner that advertises all required tags
  • Protected scope: restricts runners to protected refs if enabled
  • Concurrency: the concurrent value caps how many jobs the runner can run in parallel

Minimal config.toml knobs that affect control flow and throughput:

concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

Safe Configuration Path: Start With Shell Executor

Start with a simple, auditable pilot on a single VM using the shell executor. This keeps isolation complexity low while you validate tags, scheduling, logs, and artifact handling.

Prerequisites:

  • Linux VM with outbound HTTPS to your GitLab instance
  • A dedicated system user, for example gitlab-runner
  • A project or group runner token and a unique tag, for example local-shell

Steps (Debian/Ubuntu example):

  1. Create a dedicated user and install the runner.
sudo useradd --create-home --shell /bin/bash gitlab-runner || true
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install -y gitlab-runner
  1. Install and enable the service under the dedicated user.
sudo gitlab-runner install --user=gitlab-runner --service=systemd
sudo systemctl enable --now gitlab-runner
  1. Register a shell executor with a unique tag.
sudo -u gitlab-runner gitlab-runner register \
  --url https://gitlab.example.com/ \
  --registration-token YOUR_PROJECT_OR_GROUP_TOKEN \
  --description "local-shell" \
  --tag-list "local-shell" \
  --executor shell \
  --locked=false \
  --run-untagged=false
  1. Set conservative concurrency and confirm directories.
sudo cp /etc/gitlab-runner/config.toml /etc/gitlab-runner/config.toml.bak
sudo sed -i 's/^concurrent = .*/concurrent = 1/' /etc/gitlab-runner/config.toml
sudo systemctl restart gitlab-runner

Expected results:

  • systemctl status gitlab-runner shows active (running)
  • gitlab-runner list shows the new runner with executor: shell
  • A job tagged local-shell is picked up quickly and runs on this host

A minimal verification job (store in your project) to prove end-to-end behavior:

stages: [verify]

runner_verify:
  stage: verify
  tags: [local-shell]
  script:
    - echo "hello from runner on $(hostname)"
    - echo "workspace: $PWD"
    - uname -a
  artifacts:
    when: always
    expire_in: 1 day
    paths:
      - verify.log
  after_script:
    - echo "verification complete" | tee verify.log

Practical Example: Docker Executor

Move to the Docker executor when you need isolated, reproducible toolchains. The runner launches each job in a container based on the job's image or a default image.

Registration (runner host must have Docker installed and permission for the runner user to access the Docker socket):

sudo -u gitlab-runner gitlab-runner register \
  --url https://gitlab.example.com/ \
  --registration-token YOUR_TOKEN \
  --description "docker-shared" \
  --tag-list "docker" \
  --executor docker

Edit /etc/gitlab-runner/config.toml to set sane defaults:

runners
  name = "docker-shared"
  url = "https://gitlab.example.com/"
  token = "REDACTED"
  executor = "docker"
  [runners.docker]
    image = "alpine:3.19"
    privileged = false
    disable_cache = false
    pull_policy = ["if-not-present"]
    volumes = ["/cache"]

An example job that uses this runner:

stages: [build]

container_build:
  stage: build
  tags: [docker]
  image: alpine:3.19
  script:
    - apk add --no-cache bash
    - echo "build step in container: $(uname -a)"
  artifacts:
    paths: ["build/output.txt"]

Executor tradeoffs at a glance:

ExecutorIsolationOverheadTypical use
shellLow (host)MinimalFast pilot, trusted hosts
dockerMedium (container)ModerateReproducible toolchains
kubernetesHigh (pod)HigherElastic scale across nodes

Practical Example: Kubernetes Executor

Use the Kubernetes executor when you want to scale horizontally across a cluster. Each job runs in a pod created on demand.

Prerequisites:

  • A reachable Kubernetes cluster
  • Service account with permissions to create pods in a chosen namespace
  • Network egress from the cluster to GitLab and any registries

Register and configure (simplified):

sudo -u gitlab-runner gitlab-runner register \
  --url https://gitlab.example.com/ \
  --registration-token YOUR_TOKEN \
  --description "k8s-exec" \
  --tag-list "k8s" \
  --executor kubernetes

Then edit /etc/gitlab-runner/config.toml:

runners
  name = "k8s-exec"
  url = "https://gitlab.example.com/"
  token = "REDACTED"
  executor = "kubernetes"
  [runners.kubernetes]
    namespace = "ci"
    image = "alpine:3.19"
    service_account = "gitlab-runner"
    poll_timeout = 600

A simple job for this executor:

stages: [test]

cluster_smoke:
  stage: test
  tags: [k8s]
  image: alpine:3.19
  script:
    - echo "running in k8s pod"
    - cat /etc/os-release || true

Expected behavior:

  • The runner creates a pod per job in the ci namespace
  • Logs stream to the job details page
  • Artifacts upload to GitLab when the pod exits

Verification and Diagnostics

Confirm core health after any change:

  1. Runner service state
  • systemctl status gitlab-runner
  • journalctl -u gitlab-runner -n 200 --no-pager
  1. Runner registration
  • gitlab-runner list
  1. Network reachability
  • curl -I https://gitlab.example.com/ from the runner
  1. Job scheduling
  • Trigger the verification job with the matching tag
  • Observe that the job is picked up by the expected executor
  1. Artifacts and cache
  • Confirm the artifact is attached to the job
  • If using cache, confirm cache hits on subsequent runs

For deeper inspection (constructed examples):

sudo -u gitlab-runner ls -lah /home/gitlab-runner/builds

  • Shell executor workspace: check permissions and free space

docker info and docker images (on the runner host)

  • Docker executor image pulls and volume mounts

kubectl -n ci get pods and kubectl -n ci describe pod <name>

  • Kubernetes executor pod events

Expected verification outcomes:

  • Jobs tagged for local-shell, docker, and k8s route only to matching runners
  • The job logs show environment details as scripted
  • No stuck jobs in pending for longer than the polling interval

Failure Modes and Recovery

Common breakages and practical remedies:

SymptomLikely causeFix
Job stays PendingNo runner with matching tagsAdd correct tag or add a matching runner
Runner not respondingNetwork egress blocked or URL mismatchVerify URL, certificates, and outbound HTTPS
Permission denied in workspaceWrong user or dir permsEnsure runner user owns builds dir
Docker image pull failsRegistry auth or networkLog in to registry or open egress
Kubernetes pod CrashLoopBackOffMissing image or init script errorUse a known-good image and re-run

Recovery steps you can execute safely:

  1. Pause or unregister a misbehaving runner
  • Pause in the GitLab UI for quick isolation
  • Or run: gitlab-runner unregister --name "local-shell"
  1. Restore known-good configuration
  • Keep a backup of /etc/gitlab-runner/config.toml
  • sudo cp /etc/gitlab-runner/config.toml.bak /etc/gitlab-runner/config.toml
  • sudo systemctl restart gitlab-runner
  1. Retry a minimal verification job
  • Confirm scheduling and logs before enabling more concurrency

Rollback guidance:

  • Limit the blast radius by keeping concurrent = 1 during changes
  • Use unique tags per executor so you can redirect jobs quickly
  • Revert to the last working executor (for example, shell) while you fix Docker or Kubernetes configuration

Operations Checklist

Use this checklist after installation, after any change, and periodically:

FrequencyCheckCommand or evidence
DailyService is upsystemctl status gitlab-runner is active
DailyJobs not stuck PendingRecent jobs start within expected time
DailyDisk space OKdf -h on builds and cache paths
WeeklyRunner version driftCompare gitlab-runner --version to target
WeeklyTag alignmentJobs have tags that match available runners
WeeklyArtifact and cache healthArtifacts attached, cache hits observed
MonthlyConcurrency vs capacityAdjust concurrent based on load
MonthlySecurity reviewReview runner user, volumes, and permissions

Practical tips:

  • Keep one small verification job that you can trigger on demand for each executor
  • Back up config.toml before any change
  • Document the mapping of projects to runner tags so onboarding is predictable

Conclusion

You have seen how GitLab Runner components fit together, how control and data flow during a job, and where to look when things go wrong. Starting with a narrow shell-executor pilot lets you validate scheduling, logging, and artifacts with minimal risk. From there, you can add Docker for reproducible toolchains and Kubernetes for elastic scale. Use the verification steps after each change, watch for common failure modes, and keep the checklist handy to sustain predictable operations as your usage grows.

Related Research

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL