GitLab Runners, Executors, Tags, Registration, Autoscaling, Isolation, and Security: Concepts, Architecture, and Mental Model
Model GitLab Runner as remote-code-execution infrastructure: runner scope, eligibility, authentication, executors, isolation, autoscaling, persistence, and job identity.
Learning objectives
- Explain the control-plane/job-queue/runner-manager/executor boundary and why Runner is remote-code-execution infrastructure.
- Distinguish instance, group, and project runner scope from tags, protected state, paused state, lock state, and runner health status.
- Explain the modern runner authentication-token registration workflow and why legacy registration-token instructions are obsolete.
- Compare Docker, Kubernetes, autoscaling, instance, shell, SSH, and custom executor isolation/persistence characteristics.
- Reason about runner eligibility before a job starts and identify which control is responsible when no runner can pick up a job.
1. The problem: the pipeline is only as trustworthy as the compute that runs it
Chapter 13 ended at the point where GitLab has resolved a job's configuration, variables, permissions, and ref context. None of that code executes inside the GitLab web interface. A GitLab Runner process asks GitLab for eligible jobs and hands each accepted job to an executor. That executor might run commands directly on a persistent host, inside a container, in a Kubernetes pod, or on a newly provisioned ephemeral virtual machine.
This makes a runner a security boundary. Repository-controlled scripts can read source code, use the job token, receive eligible variables, write caches and artifacts, and reach whatever network endpoints the execution environment allows. A runner is therefore not a passive status reporter; it is remote-code-execution infrastructure.
2. Mental model: GitLab schedules; Runner executes
flowchart TD
C[Commit / MR / pipeline source] --> G[GitLab CI/CD control plane]
G --> Q[Pending job queue]
R[Runner manager
authentication token] -->|polls for eligible work| Q
Q -->|job payload + job token| R
R --> E{Executor}
E --> D[Docker container]
E --> K[Kubernetes pod]
E --> S[Shell on host]
E --> A[Ephemeral autoscaled instance]
D --> O[Logs / artifacts / cache]
K --> O
S --> O
A --> O
GitLab owns pipeline creation, job metadata, permissions, and the
pending queue. The runner manager authenticates itself to GitLab,
receives an eligible job, and creates the execution environment.
The job receives job-scoped identity such as
CI_JOB_TOKEN; the runner authentication token belongs
to runner configuration and must not be exposed to job scripts.
3. Runner identity is not job identity
| Identity | Purpose | Lifetime/location | Do not confuse it with |
|---|---|---|---|
| Runner authentication token | Authenticates the runner configuration/manager to GitLab when requesting jobs. |
Stored on the runner host in config.toml;
rotate/revoke when compromised.
|
A token that job scripts should print or consume. |
| Runner manager system ID | Distinguishes a machine/process using a reusable runner configuration. | Generated by Runner; visible as runner-manager metadata. | A project member identity. |
| Job token | Authorizes the running job to the limited GitLab resources allowed for that job/user/project context. | Delivered for the job and expires/revokes with job lifecycle. | The runner authentication token. |
| User identity | Contributes permissions to pipeline creation and some job actions. | GitLab account/session/token. | The OS account that executes commands on a runner host. |
If an attacker steals a runner authentication token, they may be able to clone/impersonate runner capacity. If a malicious job receives a job token, its GitLab access is bounded by the job-token model, but the job can still attack the runner host or network if isolation is weak.
4. Runner scope: where a runner can be considered
| Scope | Where jobs may come from | Typical owner | Current creation prerequisite |
|---|---|---|---|
| Instance runner | Projects across the GitLab instance, subject to project/instance policy. | Platform/instance team. | Administrator for Self-Managed instance-runner creation. |
| Group runner | Projects in a group and its subgroups where the runner is available. | Group/platform team. | Group Owner to create; Maintainer/Owner can view group runners. |
| Project runner | Explicitly assigned project(s). | Project/platform team. | Maintainer to create a project runner; sharing to other projects requires permission in both sides. |
Scope answers which projects can even consider this runner. It does not guarantee the runner can execute a particular job. Tags, protected status, paused state, runner health, project enablement, and capacity still apply.
5. Job-to-runner eligibility is an intersection, not a single switch
A useful beginner model is:
eligible = in_scope
AND runner_accepts_new_jobs
AND runner_is_available_to_project
AND runner_has_every_job_tag
AND untagged_policy_matches
AND protected_ref_policy_matches
AND runner_manager_is_online
AND execution_capacity_is_available
GitLab's documented tag rule is strict: if a job lists multiple tags, a runner must have all of them. A runner may have extra tags. An untagged job additionally requires a runner configured to run untagged jobs. CI/CD runner tags are unrelated to Git tags.
6. Paused, locked, protected, and status mean different things
| Control/state | Meaning | Effect on eligibility |
|---|---|---|
| Paused | Runner ignores new jobs. | Directly prevents pickup of new work. |
| Locked to current projects | Project-runner sharing control. The runner cannot be enabled for additional projects. | Does not by itself pause the runner or filter refs; limits future project assignment. |
| Protected | Runner is reserved for protected branches/tags, with current MR-policy nuances. | Filters jobs by protected-ref policy. |
| online / offline / stale / never_contacted | Observed runner-manager contact status. | Offline/stale/never-contacted capacity cannot execute a job now. |
| Run untagged jobs | Whether a runner may accept jobs whose YAML has no tags. | Filters untagged jobs independently of tag list. |
Do not use “locked” as a synonym for “protected,” and do not interpret “online” as proof that a runner matches a job's tags or scope.
7. Modern registration: create first, then authenticate the manager
The current workflow separates runner creation in GitLab from runner-manager registration on the machine. Creating the runner records ownership, scope, tags, protected/run-untagged policy, and other GitLab-side metadata. GitLab then shows a runner authentication token for registration.
# Conceptual only — never paste a real runner token into course notes or logs.
# 1) Create the project/group/instance runner in GitLab.
# 2) On the isolated runner host, keep the token out of shell history:
read -rsp "Runner authentication token: " RUNNER_AUTH_TOKEN; echo
sudo gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--token "$RUNNER_AUTH_TOKEN" \
--executor "docker" \
--docker-image "alpine:3.22"
unset RUNNER_AUTH_TOKEN
Legacy --registration-token workflows are deprecated
and scheduled for removal in GitLab 20.0. On modern instances the
old workflow may already be disabled and can return
410 Gone - runner registration disallowed. The correct
repair is migration to the runner creation/authentication-token
workflow, not re-enabling legacy registration merely to keep an old
tutorial working.
8. Executors: where the job actually runs
| Executor | Execution environment | Isolation/persistence lesson | Current lifecycle note |
|---|---|---|---|
| Docker | Container on a Runner host. | Usually cleaner isolation than shell when non-privileged; host mounts/privileged mode can collapse that boundary. |
Actively supported; supports image and
services.
|
| Kubernetes | Pods in a Kubernetes cluster. | Per-job pods can isolate filesystems, but cluster RBAC, service accounts, nodes, privileged containers, and network policy define real blast radius. | Actively supported. |
| Docker Autoscaler | Docker jobs on autoscaled instances via fleeting/taskscaler. | Can use one job per ephemeral instance to reduce persistence between jobs. | Generally available since Runner 17.1. |
| Instance | Job runs directly on autoscaled host instance. | Useful when container execution is unsuitable; host is still the job boundary. | Autoscaling executor using fleeting/taskscaler. |
| Shell | Commands execute directly on runner machine. | High host/network persistence risk; use only for trusted builds with a deliberately hardened host. | Currently in maintenance mode. |
| SSH / Custom | Remote machine over SSH / custom driver. | Isolation depends on remote/custom implementation and cleanup. | Currently in maintenance mode. |
| Docker Machine | Autoscaled Docker machines. | Historical autoscaling model. | Deprecated; migrate to current autoscaler/instance patterns. |
9. GitLab-hosted runners versus self-managed runners
On GitLab.com, GitLab-hosted runners are integrated instance runners and each normal hosted job runs in newly provisioned infrastructure. They reduce host-maintenance burden, but compute availability, machine type, platform, and quota remain service constraints. Self-managed runners give you executor, network, cache, image, and locality control—but you own patching, isolation, token protection, decommissioning, and capacity.
10. Autoscaling changes persistence, latency, and cost—not just capacity
flowchart TD Q[Pending jobs] --> M[Runner manager] M --> T[Taskscaler] T --> F[Fleeting provider plugin] F --> I1[Ephemeral instance A] F --> I2[Ephemeral instance B] I1 -->|one or bounded jobs| X1[Executor] I2 -->|one or bounded jobs| X2[Executor] X1 --> C[Cleanup / destroy] X2 --> C
Autoscaling can reduce cross-job persistence if instances are short-lived. It also introduces provisioning latency, cloud API failure modes, idle-capacity cost, quota limits, and image/bootstrap dependencies. A secure configuration often favors bounded reuse—such as one job per instance for high-risk workloads—over maximizing reuse.
11. The runner boundary includes cache, storage, and network
- Workspace: persistent shell hosts can retain source or generated files if cleanup is incomplete.
- Cache: shared or weakly keyed caches can become a cross-job/cross-branch poisoning surface.
- Container volumes: mounting host sockets or sensitive directories can bypass container isolation.
- Network: a job can reach anything the runner environment can route to unless segmentation/policy blocks it.
- Cloud metadata: runner networks should restrict metadata endpoints when jobs do not need them.
-
Runner configuration:
config.tomlcontains runner authentication material and must not be exposed to jobs or artifacts.
12. Read-only inspection before any runner change
Start in Settings → CI/CD → Runners for a project or Build → Runners for a group. Record runner ID/description, type/scope, tags, whether it runs untagged jobs, protected state, pause state, status, and version if visible. Do not copy authentication tokens or runner configuration.
# Use a token already configured for glab; do not print it.
PROJECT_ID="12345678" # disposable example ID
glab api "projects/$PROJECT_ID/runners" \
--paginate \
--jq '.[] | {id, description, runner_type, status, paused, tag_list}'
# If your role permits reading a specific runner:
glab api "runners/42" \
--jq '{id, description, runner_type, status, paused, tag_list, run_untagged, access_level}'
The API shape is evidence, not a reason to automate changes immediately. First decide which eligibility dimension you are testing.
13. DevOps connection: runner governance is execution governance
A protected variable does not help if unreviewed code can execute on a privileged persistent runner that has internal credentials mounted on the host. Conversely, a carefully isolated ephemeral runner cannot compensate for granting every project broad access to a deployment credential. Production governance composes source trust + pipeline policy + variable policy + runner eligibility + executor isolation + network/storage policy.
14. Common misconceptions
- “Online means eligible.” No: tags, scope, protection, pause state, and capacity still matter.
- “Docker means safe.” Not automatically: privileged mode, host socket mounts, host PID/network modes, or dangerous volumes can defeat isolation.
- “A project runner is always single-project.” It can be enabled for additional projects unless locked/assignment policy prevents that.
- “Runner tags are security labels.” They are routing selectors. Security comes from ownership, protection, executor isolation, and project trust.
- “The old registration token tutorial is still equivalent.” It is deprecated and may be disabled; use runner authentication tokens.
Knowledge check
A runner has tags docker,linux,gpu. Can it execute a job tagged docker,linux?
Yes, assuming every other eligibility condition matches. A runner may have extra tags; it must contain all job tags.
What does “Lock to current projects” do?
It prevents a project runner from being enabled for additional projects. It is not the same as pausing or protecting the runner.
Why is a Shell executor high risk on a shared persistent host?
Job code executes directly under the runner host account and can potentially observe or modify host state and data from other jobs/projects.
What token should a job normally use to access allowed GitLab resources?
Its job-scoped identity such as CI_JOB_TOKEN—not the runner authentication token stored in runner configuration.
Why can an online runner still leave a job pending?
The runner may be out of project scope, paused, protected for a different ref context, missing one or more job tags, disallow untagged jobs, or have no execution capacity.
What is the recommended registration model in current GitLab?
Create the runner in GitLab, obtain a runner authentication token, and register the runner manager with that token. Legacy registration tokens are deprecated.
Summary
Runner design starts with one principle: a runner executes repository-controlled code. Scope determines which projects can consider it; eligibility filters which jobs it can accept; the executor defines isolation; persistence, network, cache, and credentials define blast radius. Modern runner registration separates GitLab-side runner creation from runner-manager authentication and makes ownership auditable.
Official references
- GitLab Docs — Get started with GitLab Runner
- GitLab Docs — Manage runners and runner scope
- GitLab Docs — Configure runners, tags, and protected runners
- GitLab Docs — New runner creation/registration workflow
- GitLab Docs — Register runners
- GitLab Docs — Runner commands and unregister behavior
- GitLab Docs — Runner executors
- GitLab Docs — Docker executor
- GitLab Docs — Shell executor
- GitLab Docs — Kubernetes executor
- GitLab Docs — Docker Autoscaler executor
- GitLab Docs — Instance executor
- GitLab Docs — Self-managed runner security
- GitLab Docs — Runner fleet scaling
- GitLab Docs — Instance-group autoscaler
- GitLab Docs — GitLab-hosted runners
- GitLab Docs — Hosted runners on Linux for GitLab.com
- GitLab Docs — Hosted runners for GitLab Dedicated
- GitLab Docs — Runners API
- GitLab Docs — Token overview / runner authentication tokens
- GitLab Docs — CI/CD YAML tags
- GitLab Docs — Protected branches and CI/CD
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.