GitLab Runner Self-Hosted Setup (Registration, Scaling)

Are your pipelines stuck in queues and shared runners failing to meet security requirements? We set up your own self-hosted GitLab Runners, register them for your infrastructure, and scale them to handle the load. Our team delivers the project turnkey—from audit to support—ensuring stable operation and reliable task execution.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1359
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Your pipeline has been queued for 15 minutes while GitLab.com's shared runners are busy? Or your security team requires that build containers never leave your hosts? Our engineers have been setting up self-hosted GitLab Runners for 5 years — for projects with loads from 10 to 500+ jobs per day. Over 50 successful deployments in companies from startups to enterprise. We'll assess your CI infrastructure in one day and propose an architecture that cuts wait times dramatically.

How to set up GitLab Runner with Docker executor?

The most popular option is the Docker executor, where each job runs in an isolated container. Register a runner with a single command:

gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.company.com" \
  --token "glrt-xxxxxxxxxxxx" \
  --executor "docker" \
  --docker-image "alpine:latest" \
  --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" \
  --docker-privileged

The --docker-privileged flag is only needed for Docker-in-Docker (dind). If dind is not required, don't use it: it extends container privileges to the host, creating security risks. An alternative is to use docker:dind as a sidecar container.

Key config.toml settings

After registration, the configuration is in /etc/gitlab-runner/config.toml. The concurrent parameter determines how many jobs the runner executes simultaneously. Choose a value based on host resources: for a 4-core machine, 4–8 is reasonable. pull_policy = ["if-not-present"] reduces job startup time because images are cached locally. Without this setting, the runner performs docker pull for each job, slowing down the pipeline and putting load on the registry.

concurrent = 10

[[runners]]
  name = "docker-runner-prod"
  url = "https://gitlab.company.com"
  token = "glrt-xxxxxxxxxxxx"
  executor = "docker"
  [runners.docker]
    image = "alpine:latest"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
    pull_policy = ["if-not-present"]

Executor comparison

Executor Isolation Startup time Security Scaling
Shell None Instant Low Manual
Docker Yes 1–3 s Medium Manual/auto
Kubernetes Yes 10–30 s High Automatic

The Kubernetes executor wins in scaling: it automatically starts a Pod for each job, and when load peaks, the cluster autoscaler adds nodes. This is 3–5 times more efficient than a static Docker runner when daytime load is 10 times higher than nighttime.

Why choose Kubernetes executor?

For teams with variable load (CI almost unused at night, peaks during the day), the optimal setup is a runner in Kubernetes with Karpenter or Cluster Autoscaler. We deploy the runner via Helm:

helm repo add gitlab https://charts.gitlab.io
helm install gitlab-runner gitlab/gitlab-runner \
  --namespace gitlab-runner \
  --set gitlabUrl=https://gitlab.company.com \
  --set runnerToken=glrt-xxxxxxxxxxxx \
  --set rbac.create=true \
  --set runners.config="$(cat runner-config.toml)"

In runner-config.toml for the Kubernetes executor, specify resources: cpu_request: 100m, cpu_limit: 2, memory_limit: 2Gi. This ensures each job does not overload the cluster. When nodes are insufficient, Karpenter starts new ones in 60–90 seconds.

Caching strategies comparison

Cache type Write time Time savings Setup complexity
No cache 0 s 0% None
Local (disk) 1–3 s up to 30% Low
S3 (MinIO/AWS) 2–5 s up to 40% Medium

With S3 cache, rebuild times are reduced by 35–40%, and storage costs are minimal. We integrate any S3-compatible bucket.

What's included in turnkey GitLab Runner setup?

Our standard delivery includes:

  • Selection of the optimal executor based on load profile.
  • Runner registration and config.toml configuration with correct pull, cache, and security policies.
  • S3 cache setup (MinIO, AWS S3, Yandex Object Storage) to speed up subsequent builds — saves up to 40% time on repeated jobs.
  • Integration with cluster autoscaler and Karpenter for automatic scaling.
  • RBAC policies and NetworkPolicies for secure isolation.
  • Operations documentation and runbook for typical incidents.
  • Configuration work guarantee: if something breaks within a month, we fix it for free.

This solution reduces CI infrastructure costs by 30–50% per month compared to shared runners.

Typical problems when setting up yourself

Runner disconnects after token update. Since GitLab 16.0, tokens have the glrt- format. Old runners with GR1348 stop working and require re-registration. Solution: gitlab-runner verify && gitlab-runner register with the new token.

Cache doesn't work between jobs. The path in cache.paths must be relative. For example, use - .npm/ for npm, not absolute /app/node_modules. Otherwise, the cache won't restore.

Docker socket vs dind. Mounting /var/run/docker.sock gives the job root access to Docker on the host — a serious security hole. Better to use docker:dind as a service container. Yes, it slows builds by 10–20%, but security is more important. GitLab officially recommends dind for isolated builds.

Timeline and cost

Setting up a single runner with Docker executor takes 2–4 hours if you already have a host. Configuring a scalable solution on Kubernetes with S3 cache and RBAC takes 1 to 3 days. Cost is determined after an audit of your CI load — contact us, and we'll assess the project within one business day. You get a turnkey infrastructure with a stable operation guarantee. Request a consultation, and we'll prepare a custom proposal.