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 "$DOCKERREGISTRYPASS" | 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:
shbash(orshas 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.