Chapter 04Lesson 02~150 minutes

GitLab Runner Architecture, Registration, Runner Managers, Job Execution, and Lifecycle: Guided Hands-On Workflow and Core Operations

Turn the runner model into controlled evidence. In a disposable project you will inspect existing capacity, create one narrowly scoped runner configuration, register one isolated runner manager with the current authentication-token workflow, route a uniquely tagged trusted job, observe status/version/system identity, pause and stop execution deliberately, then unregister and delete only the disposable resources you created.

Hands-onProject runnerTagged routingPause & stopRetirement

Learning objectives

  • Inspect existing runners without exposing tokens or changing runner policy.
  • Create one disposable project runner using the modern creation workflow and register one isolated manager with a runner authentication token.
  • Route a trusted synthetic job exclusively to that runner by using a unique tag and verify the assigned runner from job evidence.
  • Demonstrate the operational difference among pausing the runner, stopping the manager process, resuming service, and unregistering the manager.
  • Delete the disposable runner configuration and local runner storage only after proving no required jobs or evidence depend on it.
Lab safety boundary. Use a disposable GitLab project and a disposable local container/VM only. The job is synthetic and trusted. Do not register an employer runner, mount a production Docker socket, copy a real runner token into a file, or expose internal network credentials merely to complete this chapter.

1. Preflight: prove the project and local runtime are disposable

LAB_TAG="ch04-lab"
RUNNER_NAME="ch04-disposable-manager"
RUNNER_CONTAINER="ch04-runner-manager"
RUNNER_VOLUME="ch04-runner-config"
EVIDENCE="ch04-evidence"
mkdir -p "$EVIDENCE"

git status --short --branch
git rev-parse HEAD | tee "$EVIDENCE/head-before.txt"
docker version --format '{{.Server.Version}}'

docker ps -a --filter "name=^/${RUNNER_CONTAINER}$"
docker volume ls --filter "name=^${RUNNER_VOLUME}$"

If either exact Docker resource already exists and was not created for this lab, stop. Choose new disposable names rather than deleting unknown infrastructure. In GitLab, inspect Settings → CI/CD → Runners and record existing runner IDs/descriptions without copying any token.

2. Create one narrowly scoped project runner in GitLab

In the disposable project, open Settings → CI/CD → Runners → Create project runner. Use these deliberate settings:

  • Description: ch04-disposable-runner.
  • Tags: exactly ch04-lab.
  • Run untagged: disabled.
  • Protected: disabled for this disposable unprotected lab branch; do not alter a production protection policy for the exercise.
  • Scope: project only.

After creation, record the runner ID and routing settings. GitLab displays a runner authentication token for registration. Put it into a temporary shell variable or secure prompt input; never echo, paste into notes, or commit it.

# Example only: assign the secret in your local shell without printing it.
read -rsp "Runner authentication token: " RUNNER_AUTH_TOKEN
printf '
'
test -n "$RUNNER_AUTH_TOKEN"
# Do not run: echo "$RUNNER_AUTH_TOKEN"

3. Register one isolated manager with Runner v19.3.1

docker volume create "$RUNNER_VOLUME"

docker run --rm \
  -v "$RUNNER_VOLUME:/etc/gitlab-runner" \
  gitlab/gitlab-runner:v19.3.1 register \
  --non-interactive \
  --url "https://gitlab.com/" \
  --token "$RUNNER_AUTH_TOKEN" \
  --executor "shell" \
  --description "$RUNNER_NAME"

unset RUNNER_AUTH_TOKEN

docker run -d \
  --name "$RUNNER_CONTAINER" \
  --restart no \
  -v "$RUNNER_VOLUME:/etc/gitlab-runner" \
  gitlab/gitlab-runner:v19.3.1 run

This lab intentionally uses a Shell executor inside a disposable runner container for a synthetic trusted job and does not mount the host Docker socket. Shell executor is high risk on a shared/persistent host; Chapter 05 compares executors in depth. The container here is a disposable teaching boundary, not a recommendation for production runner architecture.

4. Verify registration without reading the token

docker exec "$RUNNER_CONTAINER" gitlab-runner --version 
docker exec "$RUNNER_CONTAINER" gitlab-runner list 
docker exec "$RUNNER_CONTAINER" gitlab-runner verify

In the GitLab runner UI, verify the runner is no longer “never contacted” and inspect its manager details if available: system ID, version, platform/architecture, contacted time, status, and job execution state. Save screenshots only if they do not reveal authentication material.

5. Route one trusted job exclusively to the lab runner

stages: [inspect]

runner_identity_probe:
  stage: inspect
  tags:
    - ch04-lab
  script:
    - printf 'pipeline_id=%s
' "$CI_PIPELINE_ID"
    - printf 'job_id=%s
' "$CI_JOB_ID"
    - printf 'sha=%s
' "$CI_COMMIT_SHA"
    - printf 'runner_id=%s
' "$CI_RUNNER_ID"
    - printf 'runner_description=%s
' "$CI_RUNNER_DESCRIPTION"
    - printf 'runner_tags=%s
' "$CI_RUNNER_TAGS"
    - printf 'runner_version=%s
' "$CI_RUNNER_VERSION"
    - printf 'runner_revision=%s
' "$CI_RUNNER_REVISION"
    - printf 'runner_arch=%s
' "$CI_RUNNER_EXECUTABLE_ARCH"
    - printf 'sha=%s
runner=%s
' "$CI_COMMIT_SHA" "$CI_RUNNER_ID" > runner-evidence.txt
  artifacts:
    expire_in: 1 day
    paths:
      - runner-evidence.txt

Because the tag is unique and “run untagged” is disabled, the expected job must match this runner’s routing policy. After execution, correlate pipeline/job/SHA and runner metadata; do not infer routing only from a green status.

6. Pause the runner and observe scheduling policy

Pause the disposable project runner in the GitLab UI. Do not stop the local manager yet. Trigger another trusted job on the same disposable branch. With no other runner carrying the unique ch04-lab tag, the job should remain pending because the runner configuration is deliberately ignoring new jobs.

Record the pending job ID and the runner’s paused state. Then resume the runner. The same queued job should become eligible without changing its YAML. This separates GitLab scheduling policy from repository configuration.

7. Stop the manager process and distinguish offline from paused

docker stop "$RUNNER_CONTAINER"
docker ps -a --filter "name=^/${RUNNER_CONTAINER}$"

The runner configuration still exists and is no longer paused, but the manager stops contacting GitLab and becomes offline after the normal status interval. Queue another uniquely tagged job and preserve its pending state. Then restart only the known container:

docker start "$RUNNER_CONTAINER"
docker logs --tail 80 "$RUNNER_CONTAINER"

When contact resumes, verify the pending job is claimed. The experiment proves that “unpaused” and “online manager” are separate conditions for execution.

8. Unregister the manager without confusing it with runner deletion

docker exec "$RUNNER_CONTAINER" gitlab-runner list

docker exec "$RUNNER_CONTAINER" \
  gitlab-runner unregister --name "$RUNNER_NAME"

docker exec "$RUNNER_CONTAINER" gitlab-runner list

With the modern authentication-token workflow, unregistering removes the manager registration/local configuration entry; the GitLab-side runner configuration can remain. Verify that distinction in the UI. Do not repeat registration merely to make the UI disappear.

9. Delete only the disposable runner configuration, then local storage

After confirming no useful jobs depend on the runner, remove the disposable runner configuration through the GitLab UI. Then remove only the exact local lab resources:

docker rm -f "$RUNNER_CONTAINER" 2>/dev/null || true

docker volume inspect "$RUNNER_VOLUME" >/dev/null 2>&1 && \
  docker volume rm "$RUNNER_VOLUME"

docker ps -a --filter "name=^/${RUNNER_CONTAINER}$"
docker volume ls --filter "name=^${RUNNER_VOLUME}$"

The final listing should show no lab container/volume. Keep non-secret evidence such as runner ID, manager system ID/version, pipeline/job IDs, and timestamps; do not retain the authentication token.

10. Challenge: classify state before changing it

Observation Correct first interpretation Do not do first
Runner is paused but manager is online GitLab policy is blocking new jobs. Reinstall Runner.
Runner unpaused, manager offline Manager/service/network is unavailable. Edit job script.
Manager online, job requests missing extra tag Eligibility mismatch. Rotate token.
Job ran on wrong broad runner Routing/scope policy is too permissive. Add retries.
Auth token appears in a log Credential exposure incident. Assume masking later fixes it.
Next lesson

Configuration, Design Choices, and Tradeoffs

Use the observed lifecycle to choose runner ownership, scope, routing, persistence, isolation, and update policy for real platforms.

Knowledge check

Why disable “run untagged” on the disposable capability runner?

What does pausing prove that stopping the manager does not?

What does stopping the manager prove?

Why does the job print CI_RUNNER_ID and version metadata but not runner tokens?

After gitlab-runner unregister in the modern workflow, why can the runner still appear in GitLab?

Official references and version notes

  • Runners — runner categories, job scheduling, GitLab-hosted versus self-managed runners, and execution flow.
  • Manage runners — project/group/instance scope, creation workflow, ownership and pause/resume operations.
  • Configure runners — tags, run_untagged, protected runners, authentication-token rotation, and routing behavior.
  • Registering runners and new runner creation workflow — current runner authentication-token registration and deprecated legacy registration-token behavior.
  • GitLab Runner commands — register, list, verify, run, stop, and unregister lifecycle commands.
  • Runner fleet planning — manager system_id identity and modern unregister/delete distinctions.
  • Runners API — runner details, managers, status, pause, job history, authentication-token reset, and deletion semantics.
  • Security for self-managed runners — remote-code-execution trust, Shell executor risk, persistent-runner residue, isolation, and credential exposure.
  • GitLab Runner documentation — current compatibility guidance recommends keeping Runner major.minor aligned with GitLab; GitLab.com users should keep self-managed runners current.
Version and compatibility note

Version-sensitive statements were rechecked against current primary GitLab documentation and the GitLab Runner release history on 2026-09-11. The latest stable Runner tag visible in the upstream release history at that verification point is v19.3.1 (2026-08-24); GitLab 19.4 is scheduled after this guide-authoring date, so executable examples pin gitlab/gitlab-runner:v19.3.1 instead of a moving latest tag. The legacy runner-registration-token workflow is deprecated and scheduled for removal in GitLab 20.0; this chapter uses runner authentication tokens and the modern creation workflow.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.