Chapter 05Lesson 01~125 minutes

Runner Executors: Shell, Docker, Docker Autoscaler, Kubernetes, SSH, and Custom Execution Models: Concepts, Architecture, and Mental Model

A runner manager answers “which GitLab Runner process accepted this job?”; an executor answers “what execution environment did that manager create or select for the job?” This lesson separates those identities and compares Shell, Docker, Docker Autoscaler, Instance-era autoscaling, Kubernetes, SSH, and Custom models by isolation, lifecycle, reproducibility, elasticity, and operational ownership.

ExecutorsTrust boundariesIsolationLifecycleReproducibility

Learning objectives

  • Explain the boundary from runner manager to executor provisioning, source/cache/artifact transfer, job process/container/pod/instance, and teardown or reuse.
  • Distinguish runner configuration/manager identity from executor type, worker identity, image, filesystem, network, volumes, privileges, and lifecycle.
  • Compare actively developed Docker, Docker Autoscaler, Instance, and Kubernetes executors with maintenance-mode Shell, SSH, and Custom paths using current GitLab guidance.
  • Explain why Shell host reuse, Docker privileged mode, host Docker socket access, and broad Kubernetes permissions can materially change the trust boundary.
  • Identify evidence that proves where a job actually ran and what isolation assumptions are justified versus merely hoped for.
Foundation rule: the runner manager and the executor are different layers. The manager authenticates to GitLab and accepts work; the executor determines how and where that accepted job runs. A secure design records both identities and never treats “runner #123” as sufficient evidence of the actual execution boundary.

1. Why executor choice is a security and operations decision

Two jobs can have identical .gitlab-ci.yml and still have radically different risk. On a Shell executor, repository-controlled commands execute directly on the runner host under the runner account. On a Docker executor, commands normally execute in a job container, but privileged mode or a mounted host Docker socket can grant host-level control. On Kubernetes, each job receives a pod, yet broad service-account permissions, host mounts, privileged containers, or unsafe cluster co-tenancy can erase much of the intended isolation.

The executor therefore answers more than “what command starts the job?” It defines provisioning, filesystem reuse, process namespace, image/runtime control, network reachability, volume exposure, teardown, observability, and which infrastructure must be trusted after running repository-controlled code.

2. Mental model: manager → executor → worker → teardown

Executor causal chain — identify the worker and teardown boundary
            flowchart TD
              A[GitLab job is eligible] --> B[Runner manager accepts job]
              B --> C[Configured executor]
              C --> D[Prepare execution environment]
              D --> E[Transfer source/cache/artifacts]
              E --> F[Run job process, container, pod, or instance]
              F --> G[Upload trace/reports/artifacts/cache]
              G --> H[Teardown, reuse, or destroy worker]
              H --> I[Manager becomes available for more work]
          

The key diagnostic question is: which layer owned the first unexpected state? A job that never matches a runner is a routing problem. A matched job whose container image cannot start is executor preparation. A running job whose test command exits 1 is script/tool behavior. A successful script followed by failed artifact upload belongs to post-build transfer—not the test itself.

3. Define the execution objects before comparing them

Object What it owns Evidence to preserve
Runner manager Long-lived GitLab Runner process/configuration that authenticates, polls, accepts jobs, and invokes an executor. Runner ID, manager/system identity, version, tags/scope/protection, config location, logs.
Executor Implementation that creates/selects the environment where a job executes. Executor name, relevant config.toml settings, feature compatibility, privilege model.
Worker The concrete host/container/pod/instance used for one or more jobs. Hostname/container ID/pod UID/instance ID, lifecycle, reuse count, image/OS.
Job image Filesystem/userland used by container-capable executors. Image reference/digest, entrypoint/shell availability, package/tool versions.
Service Companion container/process such as a database for the job. Image/digest, alias, ports, readiness evidence, logs.
Workspace Checkout/build directory visible to the job. Path, ownership, mount type, cleanup behavior, cross-job persistence risk.
Privilege boundary Kernel/host/cluster permissions that the job can exercise. UID/GID, capabilities, privileged flag, mounts, socket access, Kubernetes service account/RBAC.
Teardown policy Whether the worker is cleaned, reused, or destroyed after the job. Container/pod/instance deletion, persistent volume state, residue checks, reuse count.

4. Current executor support is not symmetric

Executor Current lifecycle Natural fit Primary caution
Docker Actively developed Reproducible containerized jobs on Docker-capable hosts. Container isolation is configurable, not absolute; privileged mode/socket mounts can collapse it.
Docker Autoscaler Actively developed Elastic Docker jobs backed by autoscaled instances through Fleeting. Cloud/Fleeting complexity, capacity policy, dedicated autoscaling resources, cost bounds.
Instance Actively developed Jobs needing full instance/OS/device access with autoscaling. Worker has host-level access; image hardening, tenancy and teardown become critical.
Kubernetes Actively developed Cloud-native fleets that create a pod per job in an existing cluster. Cluster RBAC, host mounts, privileged pods, quotas/network policy and control-plane complexity.
Shell Maintenance mode Trusted workloads requiring direct host tools/devices with minimal executor machinery. Limited isolation; arbitrary job code runs on the runner host as the runner user.
SSH Maintenance mode Exceptional remote-host execution where legacy constraints require it. Least-supported feature set, remote host residue/credentials, weak reproducibility.
Custom Maintenance mode Existing bespoke provisioning systems with a maintained driver contract. You own the driver, protocol, lifecycle, security, compatibility and failure semantics.
Do not choose by familiarity. Maintenance mode means critical security fixes continue but new executor features are not planned. For new designs, prefer an actively developed executor when it can meet the workload and trust requirements.

5. Shell executor: the job is a host process

The Shell executor runs generated job scripts locally on the machine where GitLab Runner is installed. Dependencies must exist on that host, and builds run with the permissions of the runner user unless deliberately configured otherwise. This simplicity can be useful for a trusted hardware lab, legacy toolchain, or controlled workstation—but it creates a large trust boundary.

GitLab’s current security guidance explicitly treats Shell as high risk for untrusted builds because job code can inspect or alter host state and potentially steal code or credentials left by other projects. Even without root, a job can abuse whatever the runner account can access.

# Safe evidence to collect from a trusted Shell-executor job.
printf 'executor_model=shell
'
printf 'host=%s
' "$(hostname)"
printf 'uid=%s gid=%s
' "$(id -u)" "$(id -g)"
printf 'workspace=%s
' "$CI_PROJECT_DIR"
printf 'sha=%s job=%s runner=%s
'   "$CI_COMMIT_SHA" "$CI_JOB_ID" "$CI_RUNNER_ID"
Never use this as an isolation test against an employer/production host. The course uses only trusted synthetic code and disposable infrastructure. Do not intentionally probe neighboring projects, host credentials, Docker groups, SSH agents, cloud metadata, or internal networks.

6. Docker executor: reproducible userland, configurable boundary

The Docker executor launches each job in a Docker image. A runner configuration includes a default image, while a job-level image: can override it. The executor also creates helper/service containers as needed. This gives stronger environment reproducibility than a mutable host because dependencies can be encoded in an image reference and verified by digest.

But the container is not a magical hard security boundary. privileged = true, host PID/network namespaces, broad capabilities, sensitive host volumes, or direct access to the host Docker socket can give the job extensive control over the runner host. The secure question is not “is it Docker?” but “what host resources and kernel authority does this container receive?”

docker_probe:
  image: alpine:3.22
  script:
    - printf 'host=%s\n' "$(hostname)"
    - printf 'uid=%s gid=%s\n' "$(id -u)" "$(id -g)"
    - printf 'sha=%s job=%s runner=%s\n' "$CI_COMMIT_SHA" "$CI_JOB_ID" "$CI_RUNNER_ID"
    - cat /etc/os-release | sed -n '1,4p' 

For a reproducible production pipeline, replace a moving tag with an image digest after verifying the human-readable upstream release. Chapter 32 will revisit supply-chain pinning in depth.

7. Docker Autoscaler and Instance: Fleeting separates manager from elastic workers

Docker Autoscaler wraps Docker executor behavior but acquires worker instances on demand through GitLab Runner’s autoscaling stack and Fleeting provider plugins. It is documented as generally available since Runner 17.1. The Instance executor also uses Fleeting, but jobs run with full instance access rather than inside the Docker executor model.

The runner manager remains the long-lived scheduler-facing component; autoscaled instances are workers. This separation is valuable because a potentially compromised job worker can be destroyed while the manager remains outside the workload boundary. A common high-isolation pattern is one job per instance with bounded maximum capacity and destruction after use.

Autoscaled workers — manager and worker lifecycles are distinct
            flowchart TD
              G[GitLab] --> M[Runner manager]
              M --> F[Fleeting plugin]
              F --> C[Cloud autoscaling resource]
              C --> W1[Ephemeral worker 1]
              C --> W2[Ephemeral worker 2]
              W1 --> J1[Job / container]
              W2 --> J2[Job / container]
              J1 --> X1[Destroy or reuse by policy]
              J2 --> X2[Destroy or reuse by policy]
          
Cost and ownership guard: each Docker Autoscaler runner configuration should have its own dedicated autoscaling resource according to current GitLab guidance. Bound max_instances, idle capacity, reuse count, and cloud permissions. This chapter models the architecture; it does not require a paid cloud account.

8. Kubernetes executor: one job becomes a pod

The Kubernetes executor asks the Kubernetes API to create a pod for each GitLab CI job. The pod contains the build container plus GitLab helper behavior and any configured service containers. GitLab documents prepare, pre-build, build, and post-build phases: create the pod, clone/restore/download, execute user code, then upload cache/artifacts.

Per-job pods improve scheduling and resource control, but cluster isolation depends on namespace boundaries, service-account RBAC, pod security, hostPath mounts, privileged settings, node placement, network policy, and secrets. A pod that can create privileged pods or mount host files can become a cluster/host privilege problem.

Kubernetes executor — per-job pod composition
            flowchart TD
              M[Runner manager] --> API[Kubernetes API]
              API --> P[Per-job Pod]
              P --> B[Build container]
              P --> H[Helper container]
              P --> S["Service container(s)"]
              B --> L[Job script]
              H --> A[Source / cache / artifacts]
              P --> D[Pod deletion after job]
          

For production use, document the exact service account and permissions the runner needs, keep runner workloads separated from production workloads where appropriate, and retain manager/cluster events long enough to diagnose pods that disappear after failure.

9. SSH and Custom executors are exceptional paths

The SSH executor sends work to a configured remote machine. It is maintenance mode and among the least-supported executors. It can be necessary for a legacy appliance or fixed remote environment, but it inherits remote-host drift, credential management, residue, and weak portability.

The Custom executor lets operators integrate a bespoke provisioning/execution driver. That flexibility means your organization owns the complete lifecycle contract: prepare, run, cleanup, failure mapping, logging, compatibility, secret handling, and upgrade testing. Because Custom is also maintenance mode, a new platform should require a written reason why Docker, Instance, Docker Autoscaler, or Kubernetes cannot solve the need.

10. Evidence checklist: prove the boundary, not just the job status

Source identity

Pipeline source, ref and immutable CI_COMMIT_SHA.

Runner identity

Runner ID/description, manager/system identity when available, Runner version.

Executor

Exact executor type plus relevant privilege/isolation configuration.

Worker

Host/container ID, pod name/UID, or instance ID and lifecycle/reuse policy.

Image/runtime

Image release/digest or host image/OS and toolchain version.

Filesystem

Workspace path, mounts/volumes and expected persistence/cleanup.

Network

Service aliases/ports and deliberate external reachability.

Privilege

UID/GID, capabilities, privileged flag, socket/host mounts, service-account/RBAC where applicable.

Teardown

Proof that disposable container/pod/instance resources were removed or intentionally retained.

Next lesson

Guided Hands-On Workflow and Core Operations

Compare host-style and Docker execution safely, inspect boundary evidence, then model Kubernetes and Fleeting-based autoscaling without requiring paid infrastructure.

Knowledge check

What is the difference between a runner manager and an executor?

Why is Shell inappropriate for untrusted multi-project workloads?

Does a Docker container automatically provide a hard security boundary?

What does the Kubernetes executor normally create for each job?

Why distinguish a Docker Autoscaler manager from its workers?

Version and compatibility note

GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.

Official references and version notes

  • GitLab Runner executors — current executor selection guidance, compatibility matrix, actively developed paths, and maintenance-mode executors.
  • Shell executor — host-local execution, shell selection, process termination, maintenance-mode status, and security warnings.
  • Docker executor — image/service model, container lifecycle, volumes, image pull behavior, and executor configuration.
  • Docker Autoscaler executor — Fleeting-based autoscaling, Docker feature compatibility, autoscaling resources, capacity and ephemeral-worker patterns.
  • Instance executor — Fleeting-based instance provisioning, full host access, worker images, capacity, and native-step considerations.
  • Kubernetes executor — per-job Pod creation, build/helper/service containers, RBAC requirements, entrypoint behavior, and executor configuration.
  • SSH executor and Custom executor — exceptional/maintenance-mode execution models and operational constraints.
  • Security for self-managed runners — Shell risk, Docker privilege/capability guidance, network segmentation, and trust-boundary recommendations.
  • Use Docker to build Docker images — security and executor implications for Docker-in-Docker and socket/pipe binding.
Verification note

Executor behavior and status were rechecked against current primary GitLab documentation on 2026-09-11. At verification time, Docker, Docker Autoscaler, Instance, and Kubernetes are actively developed executor paths; Shell, SSH, VirtualBox, Parallels, and Custom are maintenance mode, and Docker Machine is deprecated. Docker Autoscaler is documented as generally available since GitLab Runner 17.1 and uses Fleeting plugins. Re-check these facts before future course revisions.

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.