Runners & executors
What actually runs your jobs.
GitLab decides what to run; a runner actually runs it. A runner is an agent you install that picks up jobs from GitLab, executes each in an environment chosen by its executor, and reports results back. The executor is the important choice: it determines the isolation each job gets, and therefore how contained a malicious build is. The docker and kubernetes executors — a fresh container or pod per job — are the secure default.
Shared vs project runners
Runners can be shared across the whole GitLab instance or dedicated to a project/group. Shared runners are convenient but mean many projects’ jobs run on the same machines, so a compromised job on a shared runner can affect others. For anything sensitive — especially jobs that touch production — use dedicated runners you control, and keep them separate from the runners open to forks and untrusted contributions.
[[runners]]name = "docker-runner"executor = "docker"[runners.docker]image = "alpine:3.20"privileged = false # do NOT enable unless you truly need DinDvolumes = ["/cache"] # never mount /var/run/docker.sock here
The privileged trap
To build container images inside a job you need a build engine, and the tempting shortcut is privileged: true (or mounting the host Docker socket) so the job can run Docker-in-Docker. Both hand the job root on the runner host — exactly the escape path the container courses warn about. Prefer a daemonless builder (Kaniko or BuildKit rootless) that builds images without a privileged runner at all.