When your .gitlab-ci.yml references a Docker image from a private GitLab Container Registry, the CI/CD runner needs authentication to pull it. How you set this up depends on whether the image is in the same project, a different project, or an external registry. This guide covers all three scenarios so you can pull Docker images from a private GitLab registry in your pipelines.
Prerequisites
- A GitLab project with CI/CD enabled
- A Docker image pushed to the GitLab Container Registry
- Developer role or higher on the project (for
CI_JOB_TOKENaccess)
Method 1: Same-Project Images (Automatic)
If the Docker image is in the same project as your pipeline, GitLab handles authentication automatically using the CI_JOB_TOKEN. No extra configuration needed.
Reference the image using GitLab’s predefined variables:
build:
image: $CI_REGISTRY_IMAGE:latest
script:
- echo "Running with private image"
$CI_REGISTRY_IMAGE expands to the full registry path for your project (e.g., registry.gitlab.com/your-group/your-project). The runner authenticates with CI_JOB_TOKEN behind the scenes.
You can also reference image tags and paths within the project:
image: $CI_REGISTRY_IMAGE/app:v2.1.0
Method 2: Cross-Project Images (Deploy Token)
If the image is in a different GitLab project, the job token won’t have access. Use a deploy token instead — it’s scoped to a project or group and doesn’t expire with jobs.
1. Create a deploy token
In the project that hosts the image:
- Go to Settings > Repository > Deploy tokens
- Set a name (e.g.,
ci-registry-read) - Select the read_registry scope
- Click Create deploy token
- Copy the username and token — the token is shown only once
2. Encode the credentials
Base64-encode the username and token to create the auth string:
printf "%s:%s" "deploy-token-username" "deploy-token-value" | base64 -w0
This outputs a single base64 string like ZGVwbG95LXRva2VuLXVzZXJuYW1lOmRlcGxveS10b2tlbi12YWx1ZQ==.
3. Add DOCKER_AUTH_CONFIG as a CI/CD variable
In the project that runs the pipeline:
- Go to Settings > CI/CD > Variables
- Add a new variable with key
DOCKER_AUTH_CONFIG - Set the value to the following JSON, replacing the auth string with yours:
{
"auths": {
"registry.gitlab.com": {
"auth": "ZGVwbG95LXRva2VuLXVzZXJuYW1lOmRlcGxveS10b2tlbi12YWx1ZQ=="
}
}
}
- Set the variable type to Variable (not File)
- Enable Mask variable to hide it in job logs
- Optionally enable Protect variable to restrict it to protected branches
Now your pipeline can pull images from the other project’s registry:
build:
image: registry.gitlab.com/other-group/other-project/app:latest
script:
- echo "Running with cross-project image"
Method 3: Docker Login in Docker-in-Docker Builds
If your pipeline uses Docker-in-Docker (DinD) to build or push images, you need to explicitly log in. Use CI_JOB_TOKEN for same-project access:
build-and-push:
image: docker:latest
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
$CI_REGISTRY_USER— the username for registry authentication (set automatically by GitLab)$CI_JOB_TOKEN— a short-lived token valid only for the duration of the job$CI_REGISTRY— the registry URL (e.g.,registry.gitlab.com)$CI_COMMIT_SHORT_SHA— the short commit hash, used as the image tag
For cross-project pulls in DinD, replace the login credentials with a deploy token stored in CI/CD variables:
before_script:
- docker login -u $DEPLOY_TOKEN_USER -p $DEPLOY_TOKEN $CI_REGISTRY
If your Docker builds hang during this process, see Fixing GitLab CI/CD Hangs: Building Docker Images for Lambda Runtime with MSSQL and ODBC for common causes.
Which Method Should You Use?
| Scenario | Method | Auth Type |
|---|---|---|
| Image in the same project | Method 1 | CI_JOB_TOKEN (automatic) |
| Image in a different project or group | Method 2 | Deploy token + DOCKER_AUTH_CONFIG |
| Building/pushing images (Docker-in-Docker) | Method 3 | docker login with token |
Avoid using Personal Access Tokens (PATs) in CI/CD pipelines. PATs are tied to a user account — if the user leaves or the token is compromised, pipelines break. Deploy tokens are scoped to a project or group and are the recommended approach for CI/CD.
Troubleshooting
403 Forbidden when pulling
The token doesn’t have the read_registry scope, or the user/token doesn’t have access to the project. Check the deploy token scopes and verify the project visibility settings.
DOCKER_AUTH_CONFIG not working
Make sure the variable type is set to Variable, not File. Also check that the JSON is valid — a missing comma or bracket will silently fail. Test the JSON locally with echo '$DOCKER_AUTH_CONFIG' | python3 -m json.tool.
Image not found
Double-check the full image path. The format is registry.gitlab.com/group/project/image:tag. Use $CI_REGISTRY_IMAGE in same-project pipelines to avoid typos.
Conclusion
For same-project images, GitLab handles authentication automatically — just use $CI_REGISTRY_IMAGE. For cross-project access, create a deploy token and configure DOCKER_AUTH_CONFIG. If you’re working with Docker images that have build issues in CI/CD, check out How to Remove All None Tag Docker Images to clean up dangling images from failed builds. For WSL Docker permission issues, see Resolving WSL Permission Denied When Connecting to Docker Daemon.


