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
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
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
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
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 |
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
- Implement GitLab CI/CD security scanning for Docker images
- Set up GitLab backup and disaster recovery with automated restoration
- Configure GitLab container registry cleanup policies and storage management
- Configure GitLab high availability clustering for enterprise deployments
- Integrate GitLab with Kubernetes for automated deployments
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage
usage() {
echo "Usage: $0 <gitlab_domain> <registry_domain>"
echo "Example: $0 gitlab.example.com registry.example.com"
exit 1
}
# Check arguments
if [ $# -ne 2 ]; then
usage
fi
GITLAB_DOMAIN="$1"
REGISTRY_DOMAIN="$2"
TOTAL_STEPS=8
# Logging functions
log_info() { echo -e "${GREEN}$1${NC}"; }
log_warn() { echo -e "${YELLOW}$1${NC}"; }
log_error() { echo -e "${RED}$1${NC}"; }
# Cleanup on error
cleanup() {
log_error "Installation failed. Cleaning up..."
systemctl stop gitlab-runsvdir.service 2>/dev/null || true
rm -f /etc/gitlab/gitlab.rb.bak
exit 1
}
trap cleanup ERR
# Check prerequisites
check_prereqs() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
if ! command -v curl &> /dev/null; then
log_error "curl is required but not installed"
exit 1
fi
}
# Detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum update -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "Detected distribution: $ID"
}
# Update system
update_system() {
log_info "[1/$TOTAL_STEPS] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
}
# Install dependencies
install_dependencies() {
log_info "[2/$TOTAL_STEPS] Installing dependencies..."
$PKG_INSTALL curl openssh-server ca-certificates tzdata perl
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL docker.io docker-compose-v2
else
# RHEL-based systems
if ! [ -f /etc/yum.repos.d/docker-ce.repo ]; then
if [ "$ID" = "fedora" ]; then
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
else
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
fi
fi
$PKG_INSTALL docker-ce docker-ce-cli containerd.io docker-compose-plugin
fi
systemctl enable --now docker
usermod -aG docker root 2>/dev/null || true
}
# Install GitLab CE
install_gitlab() {
log_info "[3/$TOTAL_STEPS] Installing GitLab Community Edition..."
if [ "$PKG_MGR" = "apt" ]; then
curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash
EXTERNAL_URL="https://$GITLAB_DOMAIN" $PKG_INSTALL gitlab-ce
else
curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | bash
EXTERNAL_URL="https://$GITLAB_DOMAIN" $PKG_INSTALL gitlab-ce
fi
}
# Configure SSL directories
setup_ssl() {
log_info "[4/$TOTAL_STEPS] Setting up SSL directories..."
mkdir -p /etc/gitlab/ssl
chmod 755 /etc/gitlab/ssl
chown root:root /etc/gitlab/ssl
}
# Configure GitLab
configure_gitlab() {
log_info "[5/$TOTAL_STEPS] Configuring GitLab..."
# Backup existing config
cp /etc/gitlab/gitlab.rb /etc/gitlab/gitlab.rb.bak
cat > /etc/gitlab/gitlab.rb << EOF
# External URL
external_url 'https://$GITLAB_DOMAIN'
# Registry configuration
registry_external_url 'https://$REGISTRY_DOMAIN'
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 Docker Hub mirror
registry['proxy'] = {
'remoteurl' => 'https://registry-1.docker.io'
}
# Health checks
registry['health_storagedriver_enabled'] = true
# 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
registry_nginx['ssl_certificate'] = "/etc/gitlab/ssl/$REGISTRY_DOMAIN.crt"
registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/$REGISTRY_DOMAIN.key"
# Main GitLab SSL
nginx['ssl_certificate'] = "/etc/gitlab/ssl/$GITLAB_DOMAIN.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/$GITLAB_DOMAIN.key"
EOF
chmod 644 /etc/gitlab/gitlab.rb
chown root:root /etc/gitlab/gitlab.rb
}
# Generate self-signed certificates
generate_ssl_certs() {
log_info "[6/$TOTAL_STEPS] Generating self-signed SSL certificates..."
# Generate for GitLab
openssl req -new -x509 -days 365 -nodes \
-out /etc/gitlab/ssl/$GITLAB_DOMAIN.crt \
-keyout /etc/gitlab/ssl/$GITLAB_DOMAIN.key \
-subj "/C=US/ST=State/L=City/O=Organization/CN=$GITLAB_DOMAIN"
# Generate for Registry
openssl req -new -x509 -days 365 -nodes \
-out /etc/gitlab/ssl/$REGISTRY_DOMAIN.crt \
-keyout /etc/gitlab/ssl/$REGISTRY_DOMAIN.key \
-subj "/C=US/ST=State/L=City/O=Organization/CN=$REGISTRY_DOMAIN"
chmod 600 /etc/gitlab/ssl/*.key
chmod 644 /etc/gitlab/ssl/*.crt
chown root:root /etc/gitlab/ssl/*
}
# Configure firewall
configure_firewall() {
log_info "[7/$TOTAL_STEPS] Configuring firewall..."
if [ "$PKG_MGR" = "apt" ]; then
if command -v ufw &> /dev/null; then
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 5050/tcp
ufw --force enable
fi
else
if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-port=22/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=5050/tcp
firewall-cmd --reload
fi
# Configure SELinux if enabled
if command -v setsebool &> /dev/null && getenforce 2>/dev/null | grep -q "Enforcing"; then
setsebool -P httpd_can_network_connect 1
setsebool -P httpd_can_network_relay 1
fi
fi
}
# Finalize installation
finalize_installation() {
log_info "[8/$TOTAL_STEPS] Finalizing installation..."
# Reconfigure GitLab
gitlab-ctl reconfigure
# Start services
systemctl enable gitlab-runsvdir.service
systemctl start gitlab-runsvdir.service
# Wait for services to be ready
log_info "Waiting for GitLab to start (this may take a few minutes)..."
timeout=300
while [ $timeout -gt 0 ]; do
if gitlab-ctl status | grep -q "run:"; then
break
fi
sleep 10
((timeout-=10))
done
if [ $timeout -le 0 ]; then
log_warn "GitLab may still be starting. Check 'gitlab-ctl status' manually."
fi
}
# Verification
verify_installation() {
log_info "Verifying installation..."
# Check GitLab status
if gitlab-ctl status >/dev/null 2>&1; then
log_info "✓ GitLab services are running"
else
log_warn "⚠ Some GitLab services may not be ready yet"
fi
# Check ports
if ss -tlnp | grep -q ":80 "; then
log_info "✓ HTTP port 80 is listening"
fi
if ss -tlnp | grep -q ":443 "; then
log_info "✓ HTTPS port 443 is listening"
fi
if ss -tlnp | grep -q ":5050 "; then
log_info "✓ Registry port 5050 is listening"
fi
echo ""
log_info "Installation completed successfully!"
log_info "GitLab URL: https://$GITLAB_DOMAIN"
log_info "Registry URL: https://$REGISTRY_DOMAIN:5050"
echo ""
log_warn "Initial root password: $(gitlab-rails runner "puts User.where(id: 1).first.password")" 2>/dev/null || log_warn "Get initial password with: sudo gitlab-rake \"gitlab:password:reset[root]\""
log_warn "Remember to replace self-signed certificates with proper SSL certificates"
}
# Main execution
main() {
check_prereqs
detect_distro
update_system
install_dependencies
install_gitlab
setup_ssl
configure_gitlab
generate_ssl_certs
configure_firewall
finalize_installation
verify_installation
}
main "$@"
Review the script before running. Execute with: bash install.sh