GitLab

GitLab Runner config.toml Configuration and Common CI/CD Variables

GitLab Runner configuration reference covering glrt- token registration, config.toml location, concurrent, executor, [runners.docker], cache, Docker-in-Docker, Harbor private registries, and predefined variables such as CI_COMMIT_SHA.

Version and safety scope

Last reviewed: October 26, 2026

Checked against the official GitLab documentation on October 26, 2026, when the latest GitLab Runner release was v19.5.1. Settings and defaults change between versions; before production use, check Runner advanced configuration, Registering runners, and Predefined CI/CD variables.

privileged = true, mounting /var/run/docker.sock, gitlab-runner unregister, and gitlab-runner verify --delete carry security or irreversible risks and are flagged below. Back up config.toml before changing it. Runner tokens and registry passwords are secrets; keep them out of repositories and CI logs.

Register a runner (authentication token workflow)

The current workflow creates the runner in GitLab first, then registers it with a runner authentication token:

  1. For a project runner, go to Settings > CI/CD > Runners and select Create project runner (Maintainer role required). Group and instance runners are created from group settings or the Admin Area.
  2. Set tags and a description, then create the runner. GitLab shows an authentication token starting with glrt- only briefly.
  3. Register on the runner host:
BASH
sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.com/" \
  --token "$RUNNER_TOKEN" \
  --executor "docker" \
  --docker-image alpine:latest \
  --docker-pull-policy "if-not-present" \
  --description "docker-runner"
  • Legacy registration tokens (--registration-token) are deprecated and, according to the docs, scheduled for removal in GitLab 20.0. Use authentication tokens for every new runner.
  • Tags, protected status, and run-untagged are set when the runner is created in the GitLab UI or API, not through register arguments.
  • To automate runner creation, call POST /api/v4/user/runners with an access token that has the create_runner scope, then pass the returned token to register:
BASH
curl --silent --request POST --url "https://gitlab.example.com/api/v4/user/runners" \
  --data "runner_type=project_type" \
  --data "project_id=<project_id>" \
  --data "description=docker-runner" \
  --data "tag_list=docker,linux" \
  --header "PRIVATE-TOKEN: <project_access_token>"

About gitlab_pat: config.toml has no gitlab_pat setting. The runner authenticates with the token under [[runners]] (the glrt- runner authentication token). A personal access token (glpat- prefix) is only for API automation such as creating runners; it does not belong in config.toml.

config.toml location

Install type config.toml path
Linux/macOS run as root (sudo) /etc/gitlab-runner/config.toml
Linux/macOS run as a regular user ~/.gitlab-runner/config.toml
Windows The install folder, for example C:\GitLab-Runner\config.toml
Other systems ./config.toml in the current directory
Docker container /etc/gitlab-runner/ inside the container, usually mounted from /srv/gitlab-runner/config on the host or a Docker volume

Docker container install, as shown in the official docs:

BASH
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

This hands the host Docker socket to the runner container; see the risks under “Docker-in-Docker vs socket binding”.

Reloading: the runner checks config.toml every 3 seconds and reloads changes automatically, or on SIGHUP. Most settings need no restart; listen_address is the exception.

Common config.toml settings

TOML
concurrent = 4          # max concurrent jobs across all runners; 0 is not allowed
check_interval = 3      # seconds between job requests to GitLab; default 3
log_level = "info"      # debug / info / warn / error / fatal / panic
shutdown_timeout = 30   # seconds before forced shutdown; default 30

[[runners]]
  name = "docker-runner"                 # informational only
  url = "https://gitlab.example.com/"
  token = "glrt-REDACTED"                # authentication token, not the registration token
  executor = "docker"                    # shell, docker, kubernetes, ...
  limit = 0                              # max concurrent jobs for this runner; 0 means no limit
  request_concurrency = 1                # concurrent job requests to GitLab; default 1
  environment = ["TZ=Asia/Taipei"]       # append or override job environment variables

  [runners.docker]
    image = "alpine:latest"              # used when a job does not set an image
    privileged = false                   # disables container isolation when true; see below
    volumes = ["/cache"]                 # extra mounts, docker -v syntax
    pull_policy = "if-not-present"       # never / if-not-present / always; default always
    allowed_images = ["registry.example.com/*"]  # restrict job images; unset allows all
    shm_size = 0

  [runners.cache]
    Type = "s3"                          # s3 / gcs / azure
    Path = "runner-cache"                # prepended to the cache URL
    Shared = true                        # share cache between runners; default false
    [runners.cache.s3]
      ServerAddress = "s3.example.com"
      BucketName = "gitlab-runner-cache"
      BucketLocation = "ap-northeast-1"
      AuthenticationType = "iam"         # iam or access-key
  • concurrent caps the whole runner process; limit caps one [[runners]] entry. Both apply.
  • pull_policy = "if-not-present" speeds up job start, but stale images already on the host are not refreshed; pin tags or digests.
  • In [runners.cache.s3], AuthenticationType defaults to access-key when ServerAddress, AccessKey, and SecretKey are all set, otherwise iam. AccessKey and SecretKey are secrets; prefer an IAM role.
  • listen_address = ":9252" enables Prometheus metrics; without it, metrics are off. Changing it requires a restart.

Docker-in-Docker vs socket binding

There are two common ways to run docker build inside a job:

Approach Settings Benefit Risk
Docker-in-Docker (DinD) privileged = true, volumes = ["/certs/client", "/cache"] Each job gets its own Docker daemon, so jobs do not conflict Privileged mode effectively disables container security mechanisms and can allow escape to the host
Docker socket binding volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"], privileged = false No privileged mode; shares the host image cache Hands the host Docker daemon to jobs: container breakout, privilege escalation, or deleting the runner’s own containers

Security warning: both privileged = true and a mounted docker.sock give jobs close to root access on the host. Enable them only on dedicated runners for trusted projects, never on shared runners that run forks or untrusted code.

.gitlab-ci.yml for DinD with TLS:

YAML
build-image:
  image: docker:<version>-cli          # use a version your team has validated
  services:
    - docker:<version>-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .

Socket binding limitations: it cannot be combined with the docker:dind service, volume paths in jobs resolve against the host, and containers with fixed names conflict between concurrent jobs.

Private registries such as Harbor

When the runner pulls job or service images from a private registry such as Harbor, provide credentials with DOCKER_AUTH_CONFIG:

BASH
# Build the auth value; printf avoids encoding a trailing newline
printf "my_username:my_password" | openssl base64 -A
JSON
{
  "auths": {
    "harbor.example.com": {
      "auth": "<base64 of username:password>"
    }
  }
}
  • Set it as a project or group CI/CD variable named DOCKER_AUTH_CONFIG (masked is recommended), in [[runners]] environment (escape the double quotes), or in .docker/config.json on the runner host.
  • Lookup order: /root/.docker/config.json → CI/CD variable → environment in config.toml → $HOME/.docker/config.json of the user running the runner.
  • The auths key must match the image reference exactly; for harbor.example.com:5000/..., the key must be harbor.example.com:5000.
  • Prefer a Harbor robot account scoped to the projects it needs.

To push to the built-in GitLab Container Registry, use the predefined variables:

YAML
script:
  - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
  - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

CI_REGISTRY_PASSWORD equals CI_JOB_TOKEN and expires when the job ends; use a deploy token for long-lived access. Pushing to Harbor requires your own masked CI/CD variables holding robot account credentials.

Useful gitlab-runner commands

BASH
sudo gitlab-runner list        # list runners in config.toml
sudo gitlab-runner verify      # check registered runners can reach GitLab
sudo gitlab-runner status      # service status; exit code 0 when running
sudo gitlab-runner restart     # stop, then start the service
sudo gitlab-runner start
sudo gitlab-runner stop

# Reload config.toml without a restart
sudo kill -SIGHUP <main_runner_pid>

Irreversible commands (back up config.toml first):

BASH
# Remove runners that no longer exist in GitLab; rewrites config.toml
sudo gitlab-runner verify --delete

# Unregister by name; with duplicate names only the first is removed
sudo gitlab-runner unregister --name docker-runner

# Unregister by URL and token
sudo gitlab-runner unregister --url "https://gitlab.example.com/" --token "$RUNNER_TOKEN"

# Unregister every runner
sudo gitlab-runner unregister --all-runners
  • For runners created in the GitLab UI or API, unregister removes only the runner manager; delete the runner in the UI or through the API to remove it completely.
  • Without sudo, the runner runs in user mode and uses ~/.gitlab-runner/config.toml, a common cause of “runner not found”.
  • Do not use killall or pkill on runners with the shell or docker executor; it can break signal handling. Send SIGQUIT for a graceful stop.

Common predefined CI/CD variables

Variable Value Notes
CI_COMMIT_SHA Full commit hash the pipeline builds
CI_COMMIT_SHORT_SHA First 8 characters of CI_COMMIT_SHA Common image tag
CI_COMMIT_REF_NAME Branch or tag name being built
CI_COMMIT_REF_SLUG CI_COMMIT_REF_NAME lowercased, non-alphanumerics replaced with -, max 63 bytes Safe for URLs, hostnames, image tags
CI_COMMIT_BRANCH Branch name Not available in merge request or tag pipelines
CI_COMMIT_TAG Tag name Only in tag pipelines
CI_DEFAULT_BRANCH Project default branch name
CI_PIPELINE_SOURCE How the pipeline was triggered For example push, merge_request_event, schedule, web, api, trigger, parent_pipeline
CI_PIPELINE_ID Pipeline ID, unique across the instance Job-only
CI_JOB_ID Job ID, unique across the instance Job-only
CI_REGISTRY Container Registry address (<host>[:<port>]) Only when the registry is enabled for the instance
CI_REGISTRY_IMAGE Base image path for the project (<host>[:<port>]/<project_full_path>) Only when the registry is enabled for the project
CI_REGISTRY_USER / CI_REGISTRY_PASSWORD Credentials for pushing to the project registry Password equals CI_JOB_TOKEN and expires with the job
CI_PROJECT_DIR Full path where the repository is cloned and the job runs Follows builds_dir when set
CI_PROJECT_NAME / CI_PROJECT_PATH Project directory name / namespace plus project name
CI_JOB_TOKEN Token for certain API endpoints Valid only while the job runs; never print it
CI_ENVIRONMENT_NAME Job environment name Only when environment:name is set
CI_MERGE_REQUEST_IID Merge request ID within the project Only in merge request pipelines
CI_SERVER_URL Base GitLab instance URL, including protocol and port
GITLAB_USER_LOGIN Username of the user who started the pipeline For manual jobs, the user who started the job

A common pattern for pipeline-type rules:

YAML
deploy:
  script:
    - echo "Deploy $CI_COMMIT_SHORT_SHA to $CI_ENVIRONMENT_NAME"
  environment:
    name: production
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"   # skip scheduled pipelines
      when: never
    - if: $CI_COMMIT_TAG                       # deploy only from tag pipelines

Further reading: for full .gitlab-ci.yml, Harbor, and multi-environment deployment examples, see the GitLab CI/CD guide.