Introduction
To pull container images from GitLab's Container Registry in Kubernetes, you need to create a Kubernetes Secret containing your GitLab credentials and reference it as an imagePullSecrets in your pod or deployment spec. The secret stores the registry URL (registry.gitlab.com), username, and either a personal access token or a deploy token as the password. Without this secret, Kubernetes cannot authenticate with the private registry and pod creation fails with ErrImagePull or ImagePullBackOff.
Step 1: Create a GitLab Access Token
1# Option A: Personal Access Token (PAT)
2# Go to GitLab > User Settings > Access Tokens
3# Create a token with 'read_registry' scope
4# Username: your GitLab username
5# Password: the generated token
6
7# Option B: Deploy Token (recommended for CI/CD)
8# Go to Project > Settings > Repository > Deploy Tokens
9# Create a token with 'read_registry' scope
10# Username: the auto-generated deploy token username
11# Password: the deploy token value
12
13# Option C: Group Deploy Token
14# Go to Group > Settings > Repository > Deploy Tokens
15# Works for all projects in the group
Deploy tokens are preferred over personal access tokens because they are scoped to a project or group and can be revoked without affecting the user's account.
Step 2: Create Kubernetes Secret
1# Create the docker-registry secret
2kubectl create secret docker-registry gitlab-registry \
3 --docker-server=registry.gitlab.com \
4 --docker-username=deploy-token-username \
5 --docker-password=your-deploy-token \
6 --docker-email=[email protected] \
7 --namespace=default
8
9# Verify the secret was created
10kubectl get secret gitlab-registry -o yaml
1# For self-hosted GitLab, use your registry URL
2kubectl create secret docker-registry gitlab-registry \
3 --docker-server=registry.your-gitlab.com \
4 --docker-username=deploy-token-username \
5 --docker-password=your-deploy-token \
6 --docker-email=[email protected]
The secret type docker-registry stores credentials in the format Docker expects for registry authentication.
Step 3: Reference in Pod/Deployment
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: my-app
5 namespace: default
6spec:
7 replicas: 2
8 selector:
9 matchLabels:
10 app: my-app
11 template:
12 metadata:
13 labels:
14 app: my-app
15 spec:
16 containers:
17 - name: my-app
18 image: registry.gitlab.com/mygroup/myproject:latest
19 ports:
20 - containerPort: 8080
21 imagePullSecrets:
22 - name: gitlab-registry
The imagePullSecrets field tells Kubernetes which secret to use when pulling the image. Without it, the kubelet attempts anonymous access and fails for private registries.
Creating the Secret from YAML
1# gitlab-registry-secret.yaml
2apiVersion: v1
3kind: Secret
4metadata:
5 name: gitlab-registry
6 namespace: default
7type: kubernetes.io/dockerconfigjson
8data:
9 .dockerconfigjson: <base64-encoded-docker-config>
1# Generate the base64 value
2echo -n '{"auths":{"registry.gitlab.com":{"username":"deploy-token","password":"your-token","auth":"base64(user:pass)"}}}' | base64
3
4# Or create from an existing Docker config
5kubectl create secret generic gitlab-registry \
6 --from-file=.dockerconfigjson=$HOME/.docker/config.json \
7 --type=kubernetes.io/dockerconfigjson
Setting Default imagePullSecrets per Namespace
1# Patch the default service account to always use the registry secret
2kubectl patch serviceaccount default \
3 -n my-namespace \
4 -p '{"imagePullSecrets": [{"name": "gitlab-registry"}]}'
5
6# Now pods in this namespace automatically use the secret
7# No need for imagePullSecrets in every deployment spec
1# Or via YAML
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5 name: default
6 namespace: my-namespace
7imagePullSecrets:
8 - name: gitlab-registry
Patching the default service account eliminates the need to add imagePullSecrets to every pod spec in the namespace.
GitLab CI/CD Integration
1# .gitlab-ci.yml — build and push image, then deploy to K8s
2stages:
3 - build
4 - deploy
5
6build:
7 stage: build
8 image: docker:latest
9 services:
10 - docker:dind
11 script:
12 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
13 - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
14 - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
15
16deploy:
17 stage: deploy
18 image: bitnami/kubectl:latest
19 script:
20 - kubectl set image deployment/my-app
21 my-app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
22 --namespace=production
GitLab CI provides built-in variables ($CI_REGISTRY, $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD) for registry authentication. The Kubernetes secret must already exist in the cluster for the deployed pods to pull the image.
Troubleshooting
1# Check if the pod can pull the image
2kubectl describe pod my-app-pod
3# Look for events:
4# Failed to pull image "registry.gitlab.com/...": unauthorized
5# Error: ErrImagePull
6# Error: ImagePullBackOff
7
8# Verify the secret exists and is correct
9kubectl get secret gitlab-registry -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d
10
11# Test registry access manually
12docker login registry.gitlab.com -u deploy-token -p your-token
13docker pull registry.gitlab.com/mygroup/myproject:latest
14
15# Check if the secret is referenced in the pod spec
16kubectl get pod my-app-pod -o jsonpath='{.spec.imagePullSecrets}'
17
18# Check token permissions
19# The token must have 'read_registry' scope
Multiple Registries
1# Create secrets for each registry
2# kubectl create secret docker-registry gitlab-reg ...
3# kubectl create secret docker-registry dockerhub-reg ...
4
5apiVersion: apps/v1
6kind: Deployment
7spec:
8 template:
9 spec:
10 containers:
11 - name: app
12 image: registry.gitlab.com/group/app:latest
13 - name: sidecar
14 image: docker.io/library/nginx:latest
15 imagePullSecrets:
16 - name: gitlab-reg
17 - name: dockerhub-reg
Multiple imagePullSecrets can be specified. Kubernetes tries each secret against the corresponding registry URL.
Common Pitfalls
Token without read_registry scope: Deploy tokens or personal access tokens must have the read_registry scope. A token with only api or read_repository scope cannot pull images. Check the token permissions in GitLab's UI.
Secret in wrong namespace: Kubernetes secrets are namespace-scoped. If the deployment is in namespace production but the secret is in default, the pod cannot find the secret. Create the secret in the same namespace as the deployment.
Expired deploy token: Deploy tokens can expire. When they do, pods start failing with ImagePullBackOff. Set up monitoring for token expiration and rotate tokens before they expire. Consider using tokens without expiration for long-running clusters.
Forgetting imagePullSecrets in the pod spec: Creating the secret is not enough — the pod spec must reference it via imagePullSecrets. Alternatively, patch the default service account to include the secret automatically.
Using the wrong registry URL for self-hosted GitLab: The default registry URL is registry.gitlab.com for GitLab.com. Self-hosted GitLab instances use a custom URL (often registry.your-domain.com). The --docker-server value must match the registry URL in the image reference.
Summary
Create a docker-registry secret with kubectl create secret docker-registry using GitLab credentials
Reference the secret in imagePullSecrets in your deployment spec
Use deploy tokens with read_registry scope (preferred over personal access tokens)
Patch the default service account to avoid adding imagePullSecrets to every pod spec
The secret must be in the same namespace as the deployment
Use kubectl describe pod to diagnose ErrImagePull and ImagePullBackOff errors