GitLab Container Registry
Guide for pushing and pulling Docker container images using GitLab Container Registry.
Table of Contents
- Overview
- Authentication
- Pushing Images
- Pulling Images
- Public Container Access
- Migrating from registry.uninett.no
Overview
GitLab Container Registry is a secure and private registry for Docker container images. Every GitLab project can have its own space in the registry to store and manage Docker images.
Registry URL Format
registry.gitlab.sikt.no/<group>/<project>/<container-name>:<tag>
Key Features
- Integrated with GitLab authentication
- Support for Docker and OCI-compliant images
- Cleanup policies
- Tag protection and retention rules
Authentication
Authentication is required to push images and to pull images from private projects.
For Pushing Images
Images must be pushed to a specific project's registry. Authentication is always required for pushing.
For Pulling Images
- Private projects: Authentication required
- Public projects: Allow anonymous pulls
To allow anonymous pulls both the group and project must be public. The container registry is then available to everyone with access. To check the container registry visibility go to:
Project Settings → General → Visibility, project features, permissions → Container registry
1. CI_REGISTRY_USER CI/CD variable
This variable holds a per-job user with read-write access to the container registry. Its password is also automatically created and available in CI_REGISTRY_PASSWORD env variable.
echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
2. CI/CD Job Token
The $CI_JOB_TOKEN is automatically available in GitLab CI/CD pipelines and has permissions to only pull images. It can pull from the same project, from any internal or public project, and from private projects that have added the pipeline's project to their job token allowlist.
Login in pipeline:
echo "$CI_JOB_TOKEN" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
One can also use gitlab-ci-token as the username.
3. Deploy Token
Deploy tokens are useful for automated systems and CI/CD pipelines. They can be created on project or group level.
Create deploy token:
- Go to Project Settings → Repository → Deploy Tokens
- Enter a name (e.g., "container-registry-token")
- Select scopes:
read_registry- Pull imageswrite_registry- Push images
- Click "Create deploy token"
- Important: Save the token immediately (it won't be shown again)
One can create deploy token named gitlab-deploy-token. Deploy token with this name will be
automatically exposed to project CI/CD jobs as variables:
CI_DEPLOY_USER: UsernameCI_DEPLOY_PASSWORD: Token
This token does not have to be saved in the CI/CD variables and can be used directly in the job to pull/push images:
echo "$CI_DEPLOY_PASSWORD" | docker login registry.gitlab.sikt.no -u $CI_DEPLOY_USER --password-stdin
Store in CI/CD variables:
Go to Settings → CI/CD → Variables and add:
DEPLOY_TOKEN_USERNAME- The deploy token usernameDEPLOY_TOKEN_PASSWORD- The deploy token password (mark as masked)
Use in job:
before_script:
- echo "$DEPLOY_TOKEN_PASSWORD" | docker login registry.gitlab.sikt.no -u $DEPLOY_TOKEN_USERNAME --password-stdin
4. Personal Access Token
For local development, use a personal access token:
Create personal access token:
- Go to User Settings → Access Tokens
- Create token with name (e.g., "docker-registry")
- Select scopes:
read_registry- Pull imageswrite_registry- Push images
- Set expiration date
- Click "Create personal access token"
- Save the token securely
Login locally:
echo $PERSONAL_ACCESS_TOKEN | docker login registry.gitlab.sikt.no -u <username> --password-stdin
Useful GitLab Predefined Environment Variables
The following variables are automatically available:
| Variable | Description |
|---|---|
| CI_REGISTRY | The registry URL (registry.gitlab.sikt.no) |
| CI_REGISTRY_USER | Username for registry authentication |
| CI_REGISTRY_PASSWORD | Password for the registry authentication |
| CI_REGISTRY_IMAGE | Base address for the container registry to push, pull, or tag project’s images, formatted as registry.gitlab.sikt.no/<project-full-path> |
| CI_JOB_TOKEN | Job token for authentication |
Pushing Images
From Local Computer
- Login to registry:
docker login registry.gitlab.sikt.no
# Enter username and personal access token
- Build your Docker image:
docker build -t registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag> .
- Push the image:
docker push registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag>
From GitLab CI/CD Pipeline
With Platon Docker Component
The Docker CI component provided by Platon is configured to handle the authentication to the same project the pipeline is running in out of the box. The job definition will look as follows:
# .gitlab-ci.yml
include:
- component: $CI_SERVER_FQDN/platon/ci-components/docker/docker@4
inputs:
image: $CI_REGISTRY_IMAGE
image-tag: $CI_COMMIT_SHA-$CI_PIPELINE_ID
build-and-push:
extends: .platon-docker-build
rules:
- if: $CI_PIPELINE_SOURCE == "push"
If a user wants to push the image to different project, deploy token with write_registry permission needs to be used.
To log in with the deploy token to the registry, overwrite the before_script section with:
before_script:
- echo "$DEPLOY_TOKEN_PASSWORD" | docker login $CI_REGISTRY -u $DEPLOY_TOAKEN_USERNAME --password-stdin
Defining Your Own Job
build-and-push:
stage: build
image: docker:cli
services:
- name: docker:dind
command: [ "dockerd", "--tls=false", "--host=tcp://0.0.0.0:2375", "--host=tcp://0.0.0.0:2376" ]
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
script:
# Build image
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
# Push image
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Pulling Images
From Local Computer
- Login to registry:
docker login registry.gitlab.sikt.no
# Enter username and personal access token
- Pull the image:
docker pull registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag>
From GitLab CI/CD Pipeline
When a using the image in the same project the pipeline is running in, one could just specify the image in the image: field.
For example:
run-image:
stage: run
image: "$CI_REGISTRY_IMAGE/<image-name>:<image-tag>"
script:
# your code comes here
Pulling From Different Project
If the source project (the project where the image is stored) has visibility internal or public, no extra configuration is needed. The job authenticates with its own CI_JOB_TOKEN towards the source project's container registry, so it is enough to specify the desired image in the image: field:
run-image:
stage: run
image: "$CI_REGISTRY/<source-group>/<source-project>/<image-name>:<image-tag>"
script:
# your code comes here
If the source project is private, add the destination project (the project where the image will be used) to the job token allowlist of the source project:
source project Settings → CI/CD → CI/CD job token allowlist
After that the image can be referenced in the image: field in the same way.
Pulling Image From Outside the GitLab Instance
When the use case is to share the image with external collaborators or run it outside PaaS (GitHub Action, ...) one can create deploy token in the registry with read_registry privilege and use that token when pulling the image.
Pulling Image from PaaS
If the image should run in PaaS, the credentials PaaS cluster will use to pull the image need to be configured manually.
-
Create deploy token with
read_registrypermission as described here. -
Save the deploy token's username and password in Vault under
gitlab/your-gitlab-group/your-gitlab-project. -
In the pipeline job that needs the secret, you need to add two sections. The first one, id_tokens, defines connection to Vault:
id_tokens:VAULT_ID_TOKEN:aud: "https://vault.sikt.no:8200"Then you need to tell the pipeline where in Vault it should look for the secret by adding a secrets section:
secrets:DEPLOY_TOKEN_USERNAME:token: $VAULT_ID_TOKENvault: "gitlab/your-gitlab-group/your-gitlab-project/deploy-token-username@secret"file: falseDEPLOY_TOKEN_PASSWORD:token: $VAULT_ID_TOKENvault: "gitlab/your-gitlab-group/your-gitlab-project/deploy-token-password@secret"file: false -
Create Kubernetes secret template and use the job environment variables holding the secrets from Vault. The environment variables will be substituted when the manifests are deployed to the PaaS Kubernetes cluster.
apiVersion: v1kind: Secretmetadata:name: <registry-secret-name>type: kubernetes.io/dockerconfigjsonstringData:.dockerconfigjson: |{"auths": {"registry.gitlab.sikt.no": {"username": "$DEPLOY_TOKEN_USERNAME","password": "$DEPLOY_TOKEN_PASSWORD"}}} -
Add the secret manifest to the deployment job. Here is an example for deploying to review environment:
review:extends: .reviewstage: reviewscript:- deploy deployment.yaml- deploy registry-secret.yamlAlternative to deploying the secret with
deploycommand is to append the secret in some other Kubernetes manifests e.g.deployment.yaml. -
Reference the secret in your deployment manifest with
imagePullSecret:apiVersion: apps/v1kind: Deploymentmetadata:name: my-private-app-deploymentspec:replicas: 1selector:matchLabels:app: my-private-apptemplate:metadata:labels:app: my-private-appspec:containers:- name: my-private-app-containerimage: registry.gitlab.sikt.no/<your-image>:<tag># Add the imagePullSecrets reference hereimagePullSecrets:- name: <registry-secret-name>
Public Container Access
As of now, projects on gitlab.sikt.no are restricted to private visibility. Making a group or a project public is not possible, because anonymous access exposes the GitLab instance to DDoS attacks and other malicious activity that could disturb it for all teams.
This means anonymous pulls are currently not available. To share an image with users outside the GitLab instance, use a deploy token with read_registry permission instead, see Pulling Image From Outside the GitLab Instance. The rest of this section describes how public container access works if the restriction is lifted for your group.
Private projects do not allow anonymous access to container images. To make container images available without authentication, images must be pushed to a public project. In GitLab a project can not have more open privileges than a group, which means the public project must be in public group.
Each team can create their own public group and public project to store the public images. If the public project only serves as public container registry, but the source code is stored in another project, one has to use one deploy tokens to push the image to the public project.
Pull Public Images (No Authentication Required)
Once images are in the Public Container Registry, anyone can pull them without authentication:
# No login required for pulling
docker pull registry.gitlab.sikt.no/public-container-registry/my-public-image:1.0.0
In CI/CD pipelines:
deploy:
image: registry.gitlab.sikt.no/public-container-registry/my-public-image:1.0.0
script:
- echo "Using public image, no authentication needed"
In Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
template:
spec:
containers:
- name: webapp
image: registry.gitlab.sikt.no/public-container-registry/my-public-image:1.0.0
# No imagePullSecrets needed for public images
Migrating from registry.uninett.no
registry.uninett.no is being retired. Projects still pushing there have to move their images either to a Platon managed ECR or to the GitLab Container Registry described on this page.
If the images are only deployed to Platon PaaS, the ECR is the recommended target. It is created and configured for you by the PaaS Console and the PaaS cluster is already allowed to pull from it, so no imagePullSecrets are needed. Follow Migrate to ECR image registry in that case.
The GitLab Container Registry is the better fit when the image is shared with 3rd parties outside Sikt or used in other GitLab projects' pipelines.
Migration Steps
1. Enable the Container Registry in the Project
Verify that the registry is enabled for the project:
Project Settings → General → Visibility, project features, permissions → Container registry
If the images have to be pullable without authentication, both the group and the project must be public, see Public Container Access.
2. Point the Pipeline at the New Registry
Projects connected to PaaS have CI_REGISTRY_IMAGE (and often CI_REGISTRY) defined as project CI/CD variables pointing to registry.uninett.no:
Project Settings → CI/CD → Variables
Remove those variables. GitLab then falls back to its own predefined values, where CI_REGISTRY_IMAGE is registry.gitlab.sikt.no/<project-full-path>, and the pipeline pushes to the project's own registry without any further changes to the image: inputs of the Platon components.
3. Replace the Authentication
Pipelines pushing to registry.uninett.no authenticate with the DOCKER_AUTH_CONFIG variable added by the PaaS Console. The GitLab Container Registry instead uses the per-job credentials CI_REGISTRY_USER and CI_REGISTRY_PASSWORD described in Authentication. When the Platon Docker component is used, this login happens out of the box and the job definition stays as it is.
Keep DOCKER_AUTH_CONFIG as long as any job still pulls from registry.uninett.no (for example a base image in a Dockerfile or an image: key). Remove the variable once nothing references the old registry any more.
4. Copy the Images You Still Need
New builds are pushed by the pipeline, but tags that have to survive the migration (released versions, images used by other projects) must be copied over. The straightforward way is to pull the image from the old registry, retag it and push it to the new one:
docker login registry.uninett.no
docker login registry.gitlab.sikt.no
docker pull registry.uninett.no/<group>/<project>:<tag>
docker tag registry.uninett.no/<group>/<project>:<tag> \
registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag>
docker push registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag>
The same commands work with podman instead of docker.
docker pull fetches only the architecture matching the machine that runs it, so a multi-architecture image loses its other platforms this way. If the image has to stay multi-architecture, copy it with skopeo instead. It transfers the image directly between the two registries, without a local Docker daemon, and --all includes every platform:
skopeo login registry.uninett.no
skopeo login registry.gitlab.sikt.no
skopeo copy --all \
docker://registry.uninett.no/<group>/<project>:<tag> \
docker://registry.gitlab.sikt.no/<group>/<project>/<image-name>:<tag>
The credentials for the old registry can be read from the DOCKER_AUTH_CONFIG variable of the project (Settings → CI/CD → Variables):
echo '<DOCKER_AUTH_CONFIG value>' | jq -r '.auths["registry.uninett.no"].auth' | base64 -d
The output is <username>:<password>. The tags stored for the project can then be listed with the registry API:
curl -s -u '<username>:<password>' https://registry.uninett.no/v2/<group>/<project>/tags/list
5. Update All References to the Old Registry
Search the repository for leftovers and update the Kubernetes manifests, image: keys, FROM statements, compose files and documentation:
grep -rn "registry.uninett.no" .
Remember that references can also live outside the repository, e.g. in pipelines of other projects that consume the image.
6. Grant the Consumers Access to the New Registry
The PaaS cluster is allowed to pull from registry.uninett.no in all application namespaces. This is not the case for the GitLab Container Registry, so access has to be configured explicitly:
- Platon PaaS: create a deploy token and deploy an
imagePullSecret, see Pulling Image from PaaS. Alternatively push the image to a public project, where no pull secret is needed. - Other GitLab projects: if the registry project is internal or public, the job token works out of the box. For private projects, add the consuming project to the job token allowlist. See Pulling From Different Project.
- Outside GitLab: use a deploy token with
read_registry, see Pulling Image From Outside the GitLab Instance.
7. Configure a Cleanup Policy
Unlike the ECR, the GitLab Container Registry does not clean up unused images automatically. Define a cleanup policy for the project as described in the best practices:
Project Settings → Packages and registries → Cleanup policies
8. Verify
Run the pipeline on the default branch and check that the image shows up under Deploy → Container Registry in the project. For services running in PaaS, verify that the workloads are started from the new image:
kubectl -n <namespace> get pods -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}'
FAQ
We build several images with different names, do we have to separate them by tag?
No. This limitation applies to the ECR only. In the GitLab Container Registry a project can hold several image names, so an image per component works as before:
docker build -t $CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA api/
docker push $CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA
The pipeline pushes to the new registry, but the deployment fails with ImagePullBackOff
The PaaS cluster has no credentials for the GitLab Container Registry by default. Create the pull secret as described in Pulling Image from PaaS and reference it with imagePullSecrets in the deployment manifest. The secret is namespaced, so it has to be deployed to every namespace the application runs in (production and development).
The pipeline still pushes to registry.uninett.no after the changes
The project level CI/CD variables override GitLab's predefined ones. Check Settings → CI/CD → Variables again for CI_REGISTRY_IMAGE and CI_REGISTRY.
Do we have to copy all the old images?
Only the ones that are still in use. Everything that is rebuilt by the pipeline anyway does not need to be copied, but tags referenced from Kubernetes manifests, other pipelines or by external users have to be available in the new registry before the old one is switched off.