GitLab Runners, Executors, Tags, Registration, Autoscaling, Isolation, and Security: Guided Hands-On Workflow and Core Operations
Inspect runner metadata first, run a minimal hosted-runner lab when capacity is available, diagnose a safe tag mismatch, and optionally register one isolated disposable self-managed runner.
Learning objectives
- Inspect runner inventory and eligibility metadata before changing runner configuration.
- Run a tiny deterministic job on available GitLab-hosted capacity or complete the same reasoning with a no-runner fixture.
- Create and diagnose one safe pending job caused by an impossible tag without changing runner security controls.
- Optionally create/register one isolated project runner using a runner authentication token without exposing the token.
- Prove cleanup of jobs, runner manager configuration, and GitLab-side runner records.
1. Disposable scenario and preflight
Use a disposable project such as
gitlab-ch14-runner-lab. The mandatory path never
registers privileged infrastructure. It uses existing GitLab.com
hosted capacity if available; if compute is unavailable or
quota-limited, the same exercise is completed with CI Lint, runner
inventory, and the supplied expected-state fixtures.
| Requirement | Mandatory path | Optional extension |
|---|---|---|
| Tier/offering | GitLab Free; GitLab.com is easiest for hosted-runner observations. | Any offering with an isolated self-managed runner host. |
| Role | Developer can push/run ordinary jobs; Maintainer is useful for project runner inventory/creation. | Maintainer creates a project runner; Owner may be needed for some protected-runner edits. |
| Compute | One tiny untagged job if hosted runner quota/capacity exists. | Disposable VM/container host with Docker for an isolated project runner. |
| Secrets | None. Do not create CI secrets for this chapter. | Runner authentication token is handled only on the runner host and immediately unset. |
| Production access | None. | No internal network, cloud metadata, host Docker socket, production credentials, or persistent shared workspace. |
2. Inspect runner inventory first
Open Settings → CI/CD → Runners. Record which runner categories are available, their tags, whether untagged jobs are accepted, protected state, pause state, and status. On GitLab.com, hosted instance runners may be available by default. Do not assume a runner is free just because it is visible; compute entitlements can change.
PROJECT_ID="12345678" # replace only with your disposable project's numeric ID
glab api "projects/$PROJECT_ID/runners" \
--paginate \
--jq '.[] | {id, description, runner_type, status, paused, tag_list}'
Expected shape: a JSON object per visible runner. If the array is empty, the correct conclusion is “this project currently has no visible runner through this query,” not “GitLab CI is broken.”
3. Smallest hosted-runner probe
Create a branch ch14/hosted-probe and add a job with no
runner tags. Keep the job deterministic and avoid environment dumps.
stages: [verify]
hosted_probe:
stage: verify
script:
- printf 'runner_probe=started\n'
- printf 'pipeline_source=%s\n' "$CI_PIPELINE_SOURCE"
- printf 'commit_sha=%s\n' "$CI_COMMIT_SHA"
- printf 'runner_id=%s\n' "$CI_RUNNER_ID"
- printf 'runner_description=%s\n' "$CI_RUNNER_DESCRIPTION"
- printf 'runner_tags=%s\n' "$CI_RUNNER_TAGS"
- printf 'runner_probe=complete\n'
These predefined fields are operational metadata, not secrets. Do
not print CI_JOB_TOKEN, all environment variables,
runner configuration, cloud metadata, or mounted credentials.
4. Validate before spending compute
Use the Pipeline Editor/CI Lint path from Chapter 10 first. Confirm
the YAML is valid and the expanded configuration contains one
verify job. Then commit and push:
git switch -c ch14/hosted-probe
git add .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch14: add safe runner probe"
PROBE_SHA="$(git rev-parse HEAD)"
printf 'probe_sha=%s\n' "$PROBE_SHA"
git push -u origin ch14/hosted-probe
If a hosted runner executes it, bind the evidence to pipeline ID, job ID, ref, SHA, runner ID/description/tags, and final status. If there is no runner quota/capacity, preserve the validated config and runner inventory and continue with the static eligibility exercises.
5. Intentionally create a tag mismatch
Add a second job whose tag is deliberately unique and absent from the runner inventory:
tag_mismatch_probe:
stage: verify
tags:
- ch14-no-such-runner
script:
- printf 'this_line_should_not_run_until_a_matching_runner_exists\n'
Before pushing, predict: the ordinary untagged job may run on an
eligible runner, while this job remains pending. The
pending job has not executed its script, so there is no executor log
to debug yet. The evidence is the job's requested tag plus the
runner inventory.
6. Diagnose pending from set intersection
| Question | Evidence | Conclusion example |
|---|---|---|
| Is the runner visible to this project? | Project runner list / scope. | If no, fix assignment/scope—not YAML syntax. |
| Is it accepting new jobs? | Paused state and status. | Paused/offline means no pickup. |
| Do tags match? |
Job tags vs runner tag_list.
|
Missing ch14-no-such-runner explains the lab
failure.
|
| Is the job untagged? | YAML plus runner run_untagged. |
An untagged job needs a runner that accepts untagged work. |
| Is protection filtering it? | Runner protected flag + ref/MR protection. | Protected runners do not serve arbitrary unprotected refs. |
| Is capacity the only remaining issue? | Runner online/eligible but busy. | Wait or scale capacity; do not weaken tags/protection. |
The safe repair for this lab is to remove the deliberately impossible tag from the synthetic job or cancel/delete the synthetic branch. Do not retag a production runner merely to make a tutorial turn green.
7. Interpret hosted-runner evidence correctly
On GitLab.com, normal hosted jobs run on GitLab-managed ephemeral infrastructure. The job's runner description and tags tell you which hosted runner class executed it. Do not infer the underlying processor, region, disk layout, or machine identity from a previous run as a permanent contract; hosted runner implementation can evolve.
If you explicitly select a larger/specialized runner tag, verify current tier and cost factor first. The mandatory lab needs only a tiny default/available runner.
8. Optional: create one isolated project runner
In the disposable project's
Settings → CI/CD → Runners, choose
Create project runner. Use a unique tag such as
ch14-isolated, disable “Run untagged jobs,” leave it
unprotected for this synthetic branch exercise, and name it clearly.
GitLab shows a runner authentication token for a limited time.
On the isolated host, install the current GitLab Runner and Docker by following official platform instructions. Then register without placing the token in 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
# Reads local configuration and contacts GitLab; do not cat config.toml.
sudo gitlab-runner verify
Runner tags, protected state, and run-untagged behavior are GitLab-side runner properties in the modern workflow. Do not follow old tutorials that try to encode all of those properties through legacy registration-token arguments.
9. Optional: prove exact runner routing
isolated_probe:
stage: verify
tags: [ch14-isolated]
script:
- printf 'isolated_probe=yes\n'
- printf 'commit_sha=%s\n' "$CI_COMMIT_SHA"
- printf 'runner_id=%s\n' "$CI_RUNNER_ID"
- printf 'runner_tags=%s\n' "$CI_RUNNER_TAGS"
- printf 'workspace=%s\n' "$CI_PROJECT_DIR"
Before running, predict that hosted runners without the tag are
ineligible and only the disposable project runner can accept the
job. Afterward, match the job's runner ID to the runner record in
GitLab. The test does not inspect the host filesystem outside
CI_PROJECT_DIR and does not probe the network.
10. Compare isolation without attacking the host
Use observation rather than breakout attempts:
| Observation | Docker executor | Shell executor fixture |
|---|---|---|
| Job process environment | Inside job container/process namespace. | Directly on persistent host under runner user. |
| Repository workspace | Container-mounted build directory managed by Runner. | Host filesystem build directory. |
| Package dependencies | Usually supplied by image. | Installed on host or managed manually. |
| Job-to-job persistence | Container is recreated; host-mounted/cache state may persist. | Host state can persist unless explicitly cleaned. |
| Security conclusion | Non-privileged container improves isolation, but host mounts/privileged mode matter. | Only trusted builds should use a persistent shell host. |
11. Surface-selection challenge
For each situation, choose the smallest correct control before reading the answer:
- A job requires Linux + GPU and no current runner has both tags → fix runner capability/routing, not branch protection.
-
A deployment runner should never run feature branches → use
protected-runner/ref policy and project trust, not merely a tag
named
prod. - A runner should stop taking work during host maintenance → pause it; do not delete it just to stop new jobs temporarily.
- A project runner must not be enabled for other projects → lock it to current projects; this is different from protected status.
12. Cleanup the optional runner completely
If you created a self-managed runner, remove both the runner manager
and GitLab-side runner record. With the modern UI-created runner,
gitlab-runner unregister removes the runner manager
association, not necessarily the reusable runner object itself.
# On the disposable runner host:
sudo gitlab-runner unregister --name "ch14-isolated-runner"
# Then delete the runner record in the disposable project's Runners UI.
# Finally remove the disposable VM/container or uninstall Runner.
# Do not display config.toml while collecting evidence.
If the GitLab-side runner was deleted first and stale local config
remains, use the official verification/cleanup guidance (for example
gitlab-runner verify --delete) on the disposable host.
Confirm no runner record remains and the host/config volume is
destroyed.
13. Verification checklist
- Every observed job is tied to a pipeline ID, ref, and commit SHA.
- The deliberately mismatched job is explained by its requested tag and runner inventory.
-
No token, full environment,
config.toml, cloud metadata, or host credential was printed. - If an optional runner was used, the executed job's runner ID matches the disposable runner record.
- The optional runner manager is unregistered and the GitLab runner object is deleted.
- The synthetic CI branch/config is removed or reverted after evidence capture.
Knowledge check
A job tagged ch14-no-such-runner is pending. What should you inspect before editing any runner?
Inspect the job tags and project-visible runner tag lists, then scope, pause/status, run-untagged, protection, and capacity. The lab intentionally fails at tag matching.
Why does the optional registration step read the token interactively?
To avoid embedding the runner authentication token in the repository, lesson text, command history, or logs.
If gitlab-runner unregister is used for a runner created in the UI with an authentication token, is cleanup necessarily complete?
No. It removes the runner manager association; delete the reusable runner object in GitLab as well.
Should you retag a production runner to satisfy this lab?
No. Use a disposable runner or repair/remove the synthetic job tag. Production runner routing is governance, not a lab convenience.
What evidence proves which runner executed a job?
The GitLab job details and safe predefined metadata such as CI_RUNNER_ID/description/tags, correlated with the project runner inventory.
Summary
The workflow was inspect → predict → run the smallest job → create one bounded mismatch → diagnose the eligibility dimension → optionally register isolated capacity → correlate job and runner identity → clean up both GitLab and the host. No production runner or secret had to be modified.
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.