26. Self-hosted GitLab Runner. About DevOps. Run CI jobs locally. Docker-in-Docker

Video version
(Leave your feedback on YouTube)

TL; DR

Never install GitLab Runner (or Jenkins, or anything else) on a production server. Use a separate deploy server or at least a stage server.

In the GitLab project: Build => Runners => New group runner => Enter a tag and copy the token.

I recommend using tags — it will help you understand which projects use which runners and will allow you to restrict resource usage for jobs.

Next, we deploy the runner in Docker:

run docker container
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest

/srv/gitlab-runner/config contains the config.toml configuration file.

register runner
docker exec -it gitlab-runner gitlab-runner register \
  --non-interactive \
  --name "local-dev" \
  --url "https://gitlab.com/" \
  --registration-token "secret" \
  --executor "docker" \
  --docker-image "docker:26.1.3" \
  --docker-privileged \
  --docker-volumes "/cache" \
  --docker-volumes "/certs/client" \
  --docker-shm-size 0

For Docker-in-Docker (DinD), the parameters docker-privileged and docker-volumes "/certs/client" are important.

The configuration file will be generated like this:

/srv/gitlab-runner/config/config.toml
concurrent = 10
check_interval = 0
connection_max_age = "15m0s"
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "local"
  url = "https://gitlab.com/"
  id = 41131741
  token = "secret"
  token_obtained_at = 2024-09-11T00:00:52Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "docker:26.1.3"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache", "/certs/client"]
    shm_size = 0
    network_mtu = 0

concurrent is set to 1 by default, but I recommend increasing it to run multiple pipelines simultaneously.

DinD is important when running Docker containers as part of a job. It isolates the container within another container, prevents conflicts, and provides access to Docker’s file storage.