GitLab Runner Docker Registry Integration and Authentication Orchestration

The seamless integration of GitLab Runner with Docker registries is a cornerstone of modern DevOps, enabling the automation of containerized build, test, and deployment pipelines. At its core, this process involves establishing a secure link between a runner—the agent that executes CI/CD jobs—and a container registry where the required environment images are stored. This connectivity is not merely about pulling an image but encompasses a complex layer of authentication, registration, and configuration that ensures jobs run in the correct environment without compromising security or stability. Whether utilizing GitLab.com, a self-managed instance, or a dedicated offering, the mechanism for managing how a runner interacts with a Docker registry is critical for operational success across Free, Premium, and Ultimate tiers.

Runner Registration and Lifecycle Management

Runner registration is the foundational process that links a runner instance with one or more GitLab instances. Without this registration, the runner cannot communicate with the GitLab coordinator and is therefore unable to pick up and execute jobs. This process effectively "introduces" the runner to the project or instance, establishing the trust relationship necessary for the execution of pipeline scripts.

Before initiating the registration process, specific prerequisites must be met to ensure the environment is prepared for the runner's operation. The installation of the GitLab Runner must occur on a server that is separate from the server where the GitLab instance itself is installed. This separation of concerns is a critical architectural requirement to prevent resource contention and ensure that a failure in the runner's execution environment does not crash the primary GitLab application. In scenarios where Docker is the intended executor, the GitLab Runner must be installed within a Docker container to provide a consistent and isolated execution environment.

The registration process requires a runner authentication token. This token can be obtained by creating an instance, group, or project runner through the manage runners interface. Once the runner is registered, the resulting configuration is persisted in the config.toml file, which serves as the primary configuration manifest for the runner's behavior.

For those executing registration via the command line, the primary command is sudo gitlab-runner register. However, in enterprise environments where the runner resides behind a proxy, standard registration will fail. In such cases, the user must first export the proxy environment variables to ensure the registration traffic can reach the GitLab instance.

bash export HTTP_PROXY=http://yourproxyurl:3128 export HTTPS_PROXY=http://yourproxyurl:3128 sudo -E gitlab-runner register

The -E flag is essential here as it preserves the environment variables for the command execution. Depending on the operating system and installation method, various registration commands are utilized:

  • For standard Linux installations: gitlab-runner register
  • For Windows environments: .\gitlab-runner.exe register
  • For running as a specific user: sudo -u gitlab-runner -H /usr/local/bin/gitlab-runner register

When utilizing Docker to register a containerized runner, short-lived containers are launched to perform the registration and then exit. This can be achieved using local system volume mounts or Docker volumes.

For local system volume mounts:
bash docker run --rm -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register

For Docker volume mounts:
bash docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest register

During the interactive registration process, the user must provide the GitLab URL. For GitLab.com users, this is https://gitlab.com. For self-managed users, it is the specific URL of their instance, such as https://gitlab.example.com.

Non-Interactive Registration and Configuration

For automation and Infrastructure as Code (IaC) purposes, non-interactive registration is preferred. This method allows the definition of the runner's characteristics—such as the executor and the default image—directly in the command line.

In a Linux environment using the Docker executor, a typical non-interactive command looks as follows:

bash sudo -u gitlab-runner -H /usr/local/bin/gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "$PROJECT_REGISTRATION_TOKEN" \ --executor "docker" \ --docker-image alpine:latest \ --description "docker-runner" \ --maintenance-note "Free-form maintainer notes about this runner" \ --tag-list "docker,aws" \ --run-untagged="true" \ --locked="false" \ --access-level="not_protected"

For Windows environments utilizing the docker-windows executor, the command structure is similar but targets specific Windows images:

powershell .\gitlab-runner.exe register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "$PROJECT_REGISTRATION_TOKEN" \ --executor "docker-windows" \ --docker-image mcr.microsoft.com/windows/servercore:1809_amd64 \ --description "docker-runner" \ --maintenance-note "Free-form maintainer notes about this runner" \ --tag-list "docker,aws" \ --run-untagged="true" \ --locked="false" \ --access-level="not_protected"

A critical parameter in this process is --access-level. This determines whether the runner is "protected." If a runner is intended to be a protected runner, the --access-level="ref_protected" parameter must be used. If it is not protected, --access-level="not_protected" is specified.

Docker-in-Docker (DinD) Registry Authentication

When employing Docker-in-Docker (DinD), standard authentication methods for registries often fail because each single job starts a fresh Docker daemon. This means any authentication state created in a previous step or at the runner level is lost because the internal Docker daemon is pristine.

To resolve this, two primary options are available for authenticating with a registry.

Manual Authentication via before_script

The most direct method is to perform a docker login within the before_script section of the .gitlab-ci.yml file. This ensures that every job starts by authenticating against the registry.

```yaml
default:
image: docker:24.0.5-cli
services:
- docker:24.0.5-dind
variables:
DOCKERTLSCERTDIR: "/certs"

build:
stage: build
beforescript:
- echo "$DOCKER
REGISTRYPASS" | docker login $DOCKERREGISTRY --username $DOCKERREGISTRYUSER --password-stdin
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
```

In this configuration, if the $DOCKER_REGISTRY variable is left empty or removed, the runner defaults to signing into Docker Hub. This approach is highly flexible but requires the management of registry credentials as CI/CD variables.

Administrative Configuration via config.json

For administrators who have direct access to the GitLab Runner host, a more permanent solution involves mounting the authentication configuration. By mounting a file containing the registry credentials to ~/.docker/config.json, every job picked up by the runner is pre-authenticated.

For those using the official docker:24.0.5 image, the home directory is located under /root. It is important to note that if the configuration file is mounted in this manner, any docker command that attempts to modify the ~/.docker/config.json file—such as a subsequent docker login—will fail because the file is mounted as read-only.

Advanced Registry Authentication and Credential Helpers

In complex environments, particularly those using cloud-provider registries like Amazon Elastic Container Registry (ECR), standard JSON credentials may be insufficient or insecure. In these cases, Credential Helpers are employed.

The DOCKERAUTHCONFIG Variable

One method to provide authentication is by creating a CI/CD variable named DOCKER_AUTH_CONFIG. This variable contains the content of the Docker configuration file as its value.

For general registries, the value might look like:
json { "credsStore": "osxkeychain" }

However, if the environment requires specific credential helpers for private registries, such as AWS ECR, the DOCKER_AUTH_CONFIG should be configured to point to the helper. For an image located at <aws_account_id>.dkr.ecr.<region>.amazonaws.com/private/image:latest, the configuration should be:

json { "credHelpers": { "<aws_account_id>.dkr.ecr.<region>.amazonaws.com": "ecr-login" } }

Alternatively, the credential helper can be configured for all ECR registries globally:
json { "credsStore": "ecr-login" }

When using credsStore: ecr-login, the region must be explicitly defined in the AWS shared configuration file located at ~/.aws/config, as the ECR Credential Helper requires the region to retrieve the authorization token.

Self-Managed Runner Configuration

For self-managed runners, the JSON configuration can be placed directly on the runner host at ${GITLAB_RUNNER_HOME}/.docker/config.json. The GitLab Runner reads this file and utilizes the specified helper for the repository.

A critical limitation exists when combining private registries and public ones: if credsStore is used to access all registries, pulling public images from Docker Hub may fail. This occurs because the Docker daemon attempts to use the same credentials for all registries, including those that do not require them or do not support the specific credential helper.

Docker Executor and Image Specifications

The Docker executor is the most common way to run CI/CD jobs in GitLab. It allows the job to run inside a specific Docker image, providing a clean and reproducible environment.

Image Requirements and Selection

Any image used for a CI/CD job must contain a minimal set of tools to be compatible with the GitLab Runner. Specifically, the image must have the following installed:

  • sh
  • bash (or sh as a fallback)
  • grep

By default, the executor pulls images from Docker Hub. However, the registry location and pull policy can be modified within the gitlab-runner/config.toml file, allowing the use of local images or private mirrors.

Utilizing Services and Templates

The Docker executor allows for the definition of "services"—additional containers that run alongside the main job image. This is typically used for databases or caches.

To set up a runner with pre-defined services using a template, a configuration file can be created:

toml [[runners]] [runners.docker] [[runners.docker.services]] name = "postgres:latest" [[runners.docker.services]] name = "mysql:latest"

This template is then applied during registration:

bash sudo gitlab-runner register \ --url "https://gitlab.example.com/" \ --token "$RUNNER_TOKEN" \ --description "docker-ruby:2.6" \ --executor "docker" \ --template-config /tmp/test-config.template.toml \ --docker-image ruby:3.3

In this example, the runner uses ruby:3.3 as the primary environment but provides postgres:latest and mysql:latest as accessible services during the build process.

Registry Configuration Summary Table

Method Scope Use Case Requirement
before_script login Job-level Dynamic registries / DinD Registry credentials as CI variables
config.json mount Runner-level Admin-managed runners Access to runner host filesystem
DOCKER_AUTH_CONFIG Project/Group level Private registries / Cloud Valid JSON config in CI variable
Credential Helpers Registry-specific AWS ECR / Cloud registries Helper binary in Runner $PATH

Detailed Analysis of Authentication Architectures

The choice between DOCKER_AUTH_CONFIG and config.json mounting represents a trade-off between flexibility and security. DOCKER_AUTH_CONFIG is highly flexible, allowing different projects to use different registries without changing the runner's physical configuration. However, it exposes credentials (albeit encrypted) within the GitLab CI/CD variable system.

Mounting config.json at the host level is more secure from a CI/CD variable leakage perspective but creates a rigid environment. Because the file is mounted as read-only, the runner cannot dynamically update its own credentials. This is particularly problematic in environments with short-lived tokens.

The use of Credential Helpers (like ecr-login) is the gold standard for cloud-native environments. By offloading the token retrieval to a binary in the $PATH, GitLab Runner avoids storing static secrets. Instead, it leverages the underlying IAM roles or AWS credentials available to the runner process. This architecture minimizes the risk of credential exposure and ensures that the authentication process adheres to the principle of least privilege.

The interaction between the Docker daemon and the registry is a critical failure point. When utilizing the Docker executor, the runner must be able to pull the specified image before the job even begins. If the registry requires authentication and the runner is not configured with the correct credentials (either via config.toml or the host's .docker/config.json), the job will fail with an ErrImagePull or ImagePullBackOff equivalent error.

Sources

  1. Registering runners
  2. Authenticate with registry in Docker-in-Docker
  3. Using Docker images

Related Posts