The GitLab Container Registry is an OCI-compliant, private Docker image registry built into every GitLab project and group. Hosting it on your own domain — for example registry.example.com — gives you a branded, TLS-secured place to push and pull images without depending on Docker Hub or a third-party registry. This guide shows how to enable it on a self-managed (Omnibus) GitLab instance, wire up DNS and TLS, and push images from both the CLI and CI.
Key takeaways
- The GitLab Container Registry is a built-in, OCI-compliant Docker registry — every project and group gets one with no extra software to install.
- On GitLab SaaS (gitlab.com) the registry already runs — self-hosting only applies to self-managed (Omnibus) instances.
- Put the registry on its own subdomain (e.g.
registry.example.com) viaregistry_external_urlin/etc/gitlab/gitlab.rb, then rungitlab-ctl reconfigure. - TLS is mandatory — Docker refuses non-
localhostregistries over plain HTTP. Use Let's Encrypt or bring your own certificates. - Authenticate with a personal access token, deploy token, or CI job token — never your account password.
- Control storage growth with cleanup/expiration policies plus periodic garbage collection; the backend can be local disk or S3-compatible object storage.
What is the GitLab Container Registry?
The GitLab Container Registry is a private, OCI-compliant Docker registry that is bundled with GitLab. Each project gets its own image namespace at registry.example.com/<group>/<project>, secured by the same permission model as the repository. Because it ships inside GitLab, there is no separate registry product to deploy — you only enable and expose it.
Why host the registry on your own domain?
Running the registry on a subdomain you control — rather than on Docker Hub or a shared host — gives you:
- A branded, predictable image path (
registry.example.com/team/app:tag) that matches your GitLab URL. - No external pull limits like Docker Hub's anonymous/free-tier caps.
- Data residency and access control — images stay on infrastructure you own, governed by GitLab roles and tokens.
- Tight CI integration — the same instance that runs your pipelines stores the images they build.
How does GitLab's registry compare to other options?
| Registry | Hosting | Auth | Best for |
|---|---|---|---|
| GitLab built-in | Self-managed or SaaS | GitLab tokens | Teams already on GitLab |
| Docker Hub | SaaS only | Docker ID | Public images, quick start (pull limits apply) |
| Harbor | Self-hosted | RBAC, scanning, signing | Standalone enterprise registry |
| AWS ECR / GCP Artifact Registry | Cloud-managed | IAM | Teams standardized on AWS or GCP |
If your code already lives in GitLab, the built-in registry is the lowest-friction option — it reuses your existing users, tokens, and pipelines.
What do you need before you start?
You need a self-managed GitLab Omnibus install, root/sudo on the server, and control of a domain's DNS. Plan the registry on its own subdomain so its TLS certificate and NGINX vhost stay independent of the main GitLab site.
| Setting | Value | Notes |
|---|---|---|
| Registry hostname | registry.example.com |
Dedicated subdomain |
| DNS record | A / AAAA → server IP |
Must resolve before issuing TLS |
| External URL | https://registry.example.com |
What Docker clients use |
| Listen port | 5050 (default) |
Internal registry port |
| TLS | Let's Encrypt or own certs | Required — no plain HTTP |
; DNS zone records for the registry subdomain
registry.example.com. 300 IN A 203.0.113.10
registry.example.com. 300 IN AAAA 2001:db8::10How do you enable the registry in gitlab.rb?
Edit /etc/gitlab/gitlab.rb and set the registry's external URL to its own HTTPS subdomain. The simplest, hands-off TLS path is to let Omnibus request a Let's Encrypt certificate automatically.
# /etc/gitlab/gitlab.rb
# Expose the registry on its own subdomain over HTTPS
registry_external_url 'https://registry.example.com'
# Option A: let Omnibus obtain and renew a Let's Encrypt cert
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['admin@example.com']
# The registry listens internally on port 5050 by default
registry_nginx['listen_port'] = 5050# Option B: bring your own certificates instead of Let's Encrypt
registry_nginx['ssl_certificate'] = '/etc/gitlab/ssl/registry.example.com.crt'
registry_nginx['ssl_certificate_key'] = '/etc/gitlab/ssl/registry.example.com.key'After saving the file, apply the configuration so GitLab provisions the registry and its NGINX vhost:
sudo gitlab-ctl reconfigureOnce reconfigure finishes, every project gains a Container Registry entry under Deploy (older versions: Packages and registries) that points to that project's image namespace.
Why is TLS mandatory?
Docker refuses to connect to any registry other than localhost over plain HTTP, and GitLab rejects a non-HTTPS registry_external_url outright with this error:
RuntimeError
------------
Unsupported GitLab Registry external URL scheme: httpAlways terminate TLS in front of the registry. If you are issuing free certificates for the first time, our walkthrough on configuring SSL with Let's Encrypt and NGINX covers the certbot flow in detail.
How do you log in and push images?
Authenticate with a token, not your password. A personal access token (scopes read_registry / write_registry), a project deploy token, or — inside pipelines — the CI job token all work. Then build, tag against your registry host, and push.
# Log in (use a token as the password, never your account password)
docker login registry.example.com
# Build and tag the image against your registry + project path
docker build -t registry.example.com/my-group/my-app:latest .
# Push it
docker push registry.example.com/my-group/my-app:latest
# Pull it back on any host
docker pull registry.example.com/my-group/my-app:latestHow do you build and push images from GitLab CI?
In pipelines, GitLab injects registry credentials automatically. Use the predefined variables $CI_REGISTRY, $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD, and $CI_REGISTRY_IMAGE — there are no secrets to manage. This .gitlab-ci.yml builds and pushes with Docker-in-Docker:
# .gitlab-ci.yml
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"Prefer a rootless build? Swap Docker-in-Docker for Kaniko or BuildKit, which build images without a privileged daemon. For end-to-end pipeline patterns, see continuous integration and continuous delivery with GitLab and Docker and Django testing automated with self-hosted GitLab CI and Docker.
How do you keep registry storage under control?
Image layers accumulate fast. Two mechanisms keep disk usage in check:
- Cleanup / expiration policies (per project, under Settings → Packages and registries) automatically remove old or untagged tags on a schedule.
- Garbage collection reclaims the disk space those deleted tags occupied. Run it during a maintenance window:
# Reclaim disk from deleted and untagged image layers
sudo gitlab-ctl registry-garbage-collectWhere are the images actually stored?
By default the registry writes to local disk. For scale or redundancy, point it at S3-compatible object storage in gitlab.rb, then run gitlab-ctl reconfigure:
# /etc/gitlab/gitlab.rb - store images in S3-compatible object storage
registry['storage'] = {
's3' => {
'accesskey' => 'AKIA...',
'secretkey' => 'SECRET...',
'bucket' => 'my-registry-bucket',
'region' => 'ap-south-1'
}
}Putting it together
Once the registry is live, your deploy hosts can pull the exact images CI built — see how to deploy a Django project into a Docker container for a downstream example. Keeping a self-managed GitLab, its registry, TLS renewals, and garbage collection healthy is ongoing work. If you would rather hand that off, MicroPyramid has run production GitLab and container infrastructure across 50+ projects since 2014 — see our server maintenance services.
Frequently Asked Questions
Is the GitLab Container Registry free to use?
The container registry is included with both GitLab SaaS and self-managed Omnibus, available on the Free tier and up. You only provide the infrastructure (compute, disk, or object storage) it runs on. Some advanced controls live in higher GitLab tiers, but the core registry needs nothing extra.
Do I need to install separate registry software?
No. The registry is built into GitLab. On a self-managed instance you set registry_external_url in /etc/gitlab/gitlab.rb and run gitlab-ctl reconfigure — there is no Harbor or standalone Docker registry to deploy.
Can I run the registry without TLS?
No. Docker only allows plain-HTTP registries on localhost, and GitLab rejects a non-HTTPS registry_external_url with an "Unsupported GitLab Registry external URL scheme: http" error. Use a Let's Encrypt or self-signed certificate on the registry subdomain.
Should the registry share the GitLab domain or use a subdomain?
A dedicated subdomain such as registry.example.com is recommended. It keeps the registry's TLS certificate and NGINX configuration independent and avoids port conflicts with the main GitLab web app.
How do I authenticate Docker to the registry?
Run docker login registry.example.com and supply a personal access token, deploy token, or — inside pipelines — the CI job token ($CI_REGISTRY_PASSWORD) as the password. Never use your GitLab account password.
How do I stop the registry from filling up the disk?
Enable per-project cleanup/expiration policies to delete old or untagged images, then run gitlab-ctl registry-garbage-collect to reclaim the space. For large registries, move storage to an S3-compatible backend so capacity scales independently of the server.