Set up GitLab container registry mirror and proxy cache for improved performance

Intermediate 45 min Apr 24, 2026 48 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure GitLab's built-in container registry as a mirror and proxy cache to reduce Docker Hub rate limits, speed up image pulls, and improve CI/CD pipeline performance across your organization.

Prerequisites

  • Root or sudo access
  • 4GB+ RAM
  • 20GB+ available disk space
  • Valid domain name with DNS configured
  • Docker Hub account (for proxy authentication)

What this solves

GitLab's container registry can act as a mirror and proxy cache for external registries like Docker Hub, reducing pull rate limits and dramatically speeding up image downloads. This setup improves CI/CD pipeline performance by caching frequently used base images locally, while providing a centralized registry for your organization's custom images.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest versions.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install required dependencies

Install Docker, Docker Compose, and other necessary packages for GitLab registry operation.

sudo apt install -y curl openssh-server ca-certificates tzdata perl
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
sudo dnf install -y curl openssh-server ca-certificates tzdata perl
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

Install GitLab Community Edition

Add GitLab's official repository and install the Community Edition package.

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo EXTERNAL_URL="https://gitlab.example.com" apt install -y gitlab-ce
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
sudo EXTERNAL_URL="https://gitlab.example.com" dnf install -y gitlab-ce

Configure GitLab container registry

Enable the built-in container registry and configure it for mirror/proxy functionality. This configuration enables the registry on port 5050 with SSL termination.

# Enable container registry
registry_external_url 'https://registry.example.com'

Registry configuration

registry['enable'] = true registry['registry_http_addr'] = "0.0.0.0:5000" registry['debug_addr'] = "localhost:5001"

Storage configuration

registry['storage'] = { 'filesystem' => { 'rootdirectory' => "/var/opt/gitlab/gitlab-rails/shared/registry" }, 'cache' => { 'blobdescriptor' => "inmemory" }, 'delete' => { 'enabled' => true } }

Proxy configuration for mirror functionality

registry['proxy'] = { 'remoteurl' => 'https://registry-1.docker.io', 'username' => 'your-docker-hub-username', 'password' => 'your-docker-hub-token' }

Health and metrics

registry['health_storagedriver_enabled'] = true registry['middleware'] = { 'storage' => [{ 'name' => 'cloudfront', 'disabled' => false, 'options' => { 'baseurl' => 'https://registry.example.com', 'privatekey' => '/etc/gitlab/ssl/registry.key', 'keypairid' => 'APKAEIBAERJR2EXAMPLE' } }] }

Nginx settings for registry

nginx['enable'] = true registry_nginx['enable'] = true registry_nginx['listen_port'] = 5050 registry_nginx['listen_https'] = true registry_nginx['redirect_http_to_https'] = true

Configure SSL certificates

Generate or install SSL certificates for secure registry access. This example uses Let's Encrypt certificates.

sudo mkdir -p /etc/gitlab/ssl
sudo chmod 755 /etc/gitlab/ssl

For Let's Encrypt certificates:

sudo apt install -y certbot
sudo certbot certonly --standalone -d registry.example.com
sudo cp /etc/letsencrypt/live/registry.example.com/fullchain.pem /etc/gitlab/ssl/registry.example.com.crt
sudo cp /etc/letsencrypt/live/registry.example.com/privkey.pem /etc/gitlab/ssl/registry.example.com.key
sudo chmod 600 /etc/gitlab/ssl/registry.example.com.key
sudo chown root:root /etc/gitlab/ssl/*

Configure proxy cache settings

Add advanced proxy cache configuration to improve performance and reduce external registry calls.

# Advanced proxy cache configuration
registry['proxy'] = {
  'remoteurl' => 'https://registry-1.docker.io',
  'username' => 'your-docker-hub-username',
  'password' => 'your-docker-hub-token',
  'ttl' => '168h0m0s',  # 7 days cache
  'size' => '10737418240'  # 10GB cache size
}

Registry cache configuration

registry['storage']['cache'] = { 'blobdescriptor' => 'redis', 'blobdescriptorsize' => '10000' }

Redis configuration for metadata caching

redis['enable'] = true redis['bind'] = '127.0.0.1' redis['port'] = 6379 redis['tcp_keepalive'] = 60 redis['tcp_timeout'] = 60 redis['maxmemory'] = '1gb' redis['maxmemory_policy'] = 'allkeys-lru'

Enable garbage collection

registry['gc'] = { 'numworkers' => 3, 'disabled' => false }

Configure authentication and authorization

Set up token-based authentication and configure access controls for the registry.

# Registry authentication
registry['auth_token_realm'] = "https://gitlab.example.com/jwt/auth"
registry['auth_token_service'] = "container_registry"
registry['auth_token_issuer'] = "gitlab-issuer"
registry['auth_token_rootcertbundle'] = "/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.crt"

Enable registry authentication

gitlab_rails['registry_enabled'] = true gitlab_rails['registry_host'] = "registry.example.com" gitlab_rails['registry_port'] = "5050" gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"

JWT token configuration

gitlab_rails['registry_key_path'] = "/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key"

Configure Docker daemon for mirror usage

Configure Docker clients to use your GitLab registry as a mirror for Docker Hub pulls.

{
  "registry-mirrors": [
    "https://registry.example.com"
  ],
  "insecure-registries": [],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "experimental": false
}

Restart Docker to apply the mirror configuration:

sudo systemctl restart docker

Apply GitLab configuration

Reconfigure GitLab to apply all registry and proxy settings.

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

Configure firewall rules

Open the necessary ports for registry access and GitLab web interface.

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 5050/tcp
sudo ufw --force enable
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=5050/tcp
sudo firewall-cmd --reload

Set up monitoring and health checks

Configure health monitoring and metrics collection for the registry.

# Enable registry health endpoint
registry['health'] = {
  'storagedriver' => {
    'enabled' => true,
    'interval' => '10s',
    'threshold' => 3
  }
}

Enable Prometheus metrics

registry['debug_addr'] = 'localhost:5001' registry['log_level'] = 'info' registry['log_formatter'] = 'json'

GitLab monitoring

gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '::1/128'] prometheus['enable'] = true prometheus['monitor_kubernetes'] = false

Apply the monitoring configuration:

sudo gitlab-ctl reconfigure

Verify your setup

Test the registry mirror and proxy functionality with these verification commands.

# Check GitLab services status
sudo gitlab-ctl status

Verify registry health endpoint

curl -f http://localhost:5001/debug/health

Test Docker login to your registry

docker login registry.example.com:5050

Pull an image through the mirror

docker pull registry.example.com:5050/library/alpine:latest

Check registry logs

sudo gitlab-ctl tail registry

Check that the proxy cache is working:

# View registry access logs
sudo tail -f /var/log/gitlab/registry/current

Check cache storage usage

du -sh /var/opt/gitlab/gitlab-rails/shared/registry

Test mirror functionality by pulling common images

docker pull nginx:latest docker pull postgres:13

Configure GitLab CI/CD runners

Configure your CI/CD runners to use the local registry mirror for faster builds. This configuration reduces external dependencies and speeds up pipeline execution.

Update GitLab Runner configuration

Modify runner configuration to use your registry mirror as the default for base images.

[[runners]]
  name = "docker-runner"
  url = "https://gitlab.example.com"
  token = "your-runner-token"
  executor = "docker"
  
  [runners.docker]
    tls_verify = false
    image = "registry.example.com:5050/library/alpine:latest"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
    
    # Use local registry as mirror
    pull_policy = ["if-not-present"]
    
  [runners.cache]
    Type = "s3"
    Shared = true

Create CI/CD configuration template

Provide teams with a .gitlab-ci.yml template that uses the local registry mirror.

variables:
  # Use local registry mirror for faster pulls
  DOCKER_REGISTRY: "registry.example.com:5050"
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

stages:
  - build
  - test
  - deploy

build:
  stage: build
  image: ${DOCKER_REGISTRY}/library/docker:24-dind
  services:
    - name: ${DOCKER_REGISTRY}/library/docker:24-dind
      alias: docker
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  
test:
  stage: test
  image: ${DOCKER_REGISTRY}/library/node:18-alpine
  script:
    - npm install
    - npm test

Monitor registry performance and health

Set up comprehensive monitoring to track cache hit rates, storage usage, and performance metrics.

Configure Prometheus monitoring

GitLab includes built-in Prometheus monitoring. Configure it to collect registry-specific metrics.

# Enable Prometheus for registry monitoring
prometheus['enable'] = true
prometheus['scrape_configs'] = [
  {
    'job_name' => 'gitlab-registry',
    'static_configs' => [
      {
        'targets' => ['localhost:5001']
      }
    ],
    'scrape_interval' => '15s',
    'scrape_timeout' => '10s'
  }
]

Configure alerting rules

prometheus['alertmanager_url'] = 'http://localhost:9093' prometheus['alert_rules'] = [ { 'name' => 'registry.rules', 'rules' => [ { 'alert' => 'RegistryDown', 'expr' => 'up{job="gitlab-registry"} == 0', 'for' => '5m', 'labels' => {'severity' => 'critical'}, 'annotations' => { 'summary' => 'GitLab Registry is down', 'description' => 'The GitLab container registry has been down for more than 5 minutes.' } } ] } ]

Apply the monitoring configuration:

sudo gitlab-ctl reconfigure

Set up log aggregation

Configure centralized logging for registry operations and access patterns.

# Registry logging configuration
registry['log_level'] = 'info'
registry['log_formatter'] = 'json'
registry['accesslog'] = {
  'disabled' => false,
  'formatter' => 'json'
}

Enable audit logging

gitlab_rails['audit_events_enabled'] = true

Configure external log shipping (optional)

logging['svlogd_prefix'] = "gitlab" logging['svlogd_size'] = 200 1024 1024 # 200MB logging['svlogd_num'] = 30 logging['udp_log_shipping_host'] = '203.0.113.50' logging['udp_log_shipping_port'] = 514

Set up cleanup and maintenance

Configure automated cleanup policies to manage storage usage and maintain optimal performance.

Configure garbage collection

Set up automated garbage collection to clean up orphaned registry data.

# GitLab Registry Garbage Collection - Run daily at 2 AM
0 2   * root /opt/gitlab/bin/gitlab-ctl registry-garbage-collect

You can also run garbage collection manually:

# Dry run to see what would be cleaned
sudo gitlab-ctl registry-garbage-collect -d

Actually perform cleanup

sudo gitlab-ctl registry-garbage-collect

Configure storage cleanup policies

Set up project-level cleanup policies to automatically remove old container images. This helps with our existing GitLab container registry cleanup policies tutorial.

# Enable cleanup policies in GitLab Rails console
sudo gitlab-rails console

In Rails console:

Feature.enable(:container_expiration_policies_enable_historic_entries) Project.find_each do |project| next unless project.container_expiration_policy project.container_expiration_policy.update!( enabled: true, cadence: '1d', older_than: '30d', keep_n: 10, name_regex_keep: 'latest|main|master' ) end

Common issues

Symptom Cause Fix
Registry returns 500 errors Storage permission issues sudo chown -R git:git /var/opt/gitlab/gitlab-rails/shared/registry && sudo gitlab-ctl restart registry
Docker pulls fail with SSL errors Certificate configuration issues Verify certificate paths in /etc/gitlab/gitlab.rb and run sudo gitlab-ctl reconfigure
High storage usage No cleanup policies configured Enable garbage collection and cleanup policies as shown above
Slow image pulls Cache not configured properly Check proxy configuration and Redis connectivity: sudo gitlab-ctl tail redis
Authentication failures JWT token configuration issues Verify auth_token_* settings match between registry and GitLab Rails
Registry not accessible externally Firewall or Nginx configuration Check firewall rules and registry_nginx settings in GitLab configuration
Never use chmod 777. If you encounter permission denied errors with registry files, fix ownership with sudo chown -R git:git /var/opt/gitlab/gitlab-rails/shared/registry and use proper permissions like 755 for directories and 644 for files.

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.