Set up GitLab's integrated Docker registry with SSL/TLS encryption, security headers, and production-ready hardening for secure container image storage and distribution.
Prerequisites
- GitLab CE installed
- Domain with DNS configured
- Root or sudo access
- Docker installed
What this solves
GitLab's container registry provides secure Docker image storage integrated with your CI/CD pipelines. This tutorial configures the registry with SSL encryption, security headers, and access controls for production use.
Step-by-step configuration
Verify GitLab installation
Ensure you have a working GitLab CE installation before configuring the container registry.
sudo gitlab-ctl status
sudo gitlab-ctl reconfigure
Install SSL certificate dependencies
Install packages needed for SSL certificate management and Docker registry configuration.
sudo apt update
sudo apt install -y certbot openssl docker.io
Generate SSL certificate
Create an SSL certificate for your registry subdomain. Replace example.com with your domain.
sudo certbot certonly --standalone \
-d registry.example.com \
--email admin@example.com \
--agree-tos --non-interactive
Create registry storage directory
Set up the directory structure for container registry storage with proper permissions.
sudo mkdir -p /var/opt/gitlab/gitlab-rails/shared/registry
sudo chown git:git /var/opt/gitlab/gitlab-rails/shared/registry
sudo chmod 755 /var/opt/gitlab/gitlab-rails/shared/registry
Configure GitLab registry settings
Edit the GitLab configuration file to enable the container registry with SSL and security settings.
# Enable container registry
registry_external_url 'https://registry.example.com:5050'
Registry storage settings
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"
SSL certificate configuration
registry_nginx['ssl_certificate'] = "/etc/letsencrypt/live/registry.example.com/fullchain.pem"
registry_nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/registry.example.com/privkey.pem"
registry_nginx['ssl_protocols'] = "TLSv1.2 TLSv1.3"
registry_nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384"
Security headers
registry_nginx['custom_nginx_config'] = "include /etc/gitlab/registry_security_headers.conf;"
Registry storage cleanup
registry['storage_delete_enabled'] = true
registry['compatibility_schema1_enabled'] = false
Access control
registry['notifications'] = [{
'name' => 'webhook',
'url' => 'https://registry.example.com/hook',
'headers' => {
'Authorization' => ['Bearer token123']
}
}]
Create security headers configuration
Add security headers to protect against common web vulnerabilities.
# Security headers for GitLab registry
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;
Registry-specific headers
add_header Docker-Content-Digest $upstream_http_docker_content_digest always;
add_header Docker-Distribution-API-Version registry/2.0 always;
Configure firewall rules
Open the required ports for the container registry with specific access controls.
sudo ufw allow 5050/tcp comment 'GitLab Registry HTTPS'
sudo ufw reload
Set certificate permissions
Configure proper permissions for SSL certificates so GitLab can read them.
sudo chown root:gitlab-registry /etc/letsencrypt/live/registry.example.com/fullchain.pem
sudo chown root:gitlab-registry /etc/letsencrypt/live/registry.example.com/privkey.pem
sudo chmod 640 /etc/letsencrypt/live/registry.example.com/fullchain.pem
sudo chmod 640 /etc/letsencrypt/live/registry.example.com/privkey.pem
Configure registry authentication token
Set up JWT token authentication for secure registry access.
sudo openssl rand -base64 32 | sudo tee /var/opt/gitlab/registry-auth.key
sudo chown git:git /var/opt/gitlab/registry-auth.key
sudo chmod 600 /var/opt/gitlab/registry-auth.key
Add authentication configuration
Configure JWT authentication settings in the GitLab configuration.
# Add JWT authentication settings
registry['auth_token_realm'] = "https://registry.example.com/jwt/auth"
registry['auth_token_service'] = "container_registry"
registry['auth_token_issuer'] = "gitlab-issuer"
registry['auth_token_rootcertbundle'] = "/var/opt/gitlab/registry-auth.key"
Health check configuration
registry['health_storagedriver_enabled'] = true
registry['health_storagedriver_interval'] = '10s'
registry['health_storagedriver_threshold'] = 3
Apply configuration changes
Reconfigure GitLab to apply all registry settings and restart services.
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart registry
sudo gitlab-ctl status
Configure automatic certificate renewal
Set up automatic SSL certificate renewal with post-renewal hooks.
sudo crontab -e
Add this line to renew certificates monthly:
0 3 1 /usr/bin/certbot renew --post-hook "gitlab-ctl restart registry" --quiet
Configure Docker client authentication
Login to GitLab registry
Test Docker client authentication using GitLab credentials or deploy tokens.
docker login registry.example.com:5050
Enter GitLab username and password or deploy token
Create GitLab deploy token
Generate a deploy token for CI/CD pipeline access to the registry.
# In GitLab UI: Project > Settings > Repository > Deploy Tokens
Create token with read_registry and write_registry scopes
Use token for Docker login:
docker login registry.example.com:5050 -u -p
Test registry functionality
Push test image
Test the registry by pushing and pulling a container image.
# Tag and push an image
docker pull alpine:latest
docker tag alpine:latest registry.example.com:5050/myproject/alpine:latest
docker push registry.example.com:5050/myproject/alpine:latest
Pull test image
Verify the registry by pulling the uploaded image.
docker rmi registry.example.com:5050/myproject/alpine:latest
docker pull registry.example.com:5050/myproject/alpine:latest
Verify your setup
# Check registry service status
sudo gitlab-ctl status registry
Verify SSL certificate
openssl s_client -connect registry.example.com:5050 -servername registry.example.com
Check registry API
curl -k https://registry.example.com:5050/v2/
Test Docker registry connectivity
docker info | grep -A 10 "Registry Mirrors"
Check registry logs
sudo gitlab-ctl tail registry
Security hardening
Configure registry resource limits
Set resource limits to prevent denial of service attacks.
# Add resource limits
registry['middleware'] = {
'registry' => [{
'name' => 'cloudfront',
'options' => {
'baseurl' => 'https://registry.example.com:5050',
'privatekey' => '/var/opt/gitlab/registry-auth.key',
'keypairid' => 'registry-key'
}
}]
}
Rate limiting
registry_nginx['rate_limiting_zone'] = '$binary_remote_addr zone=registry:10m rate=10r/m'
registry_nginx['rate_limiting_burst'] = 20
Enable audit logging
Configure comprehensive audit logging for registry access.
# Enable audit logging
registry['log_level'] = 'info'
registry['log_formatter'] = 'json'
registry['log_fields'] = {
'service' => 'registry',
'environment' => 'production'
}
Apply security configuration
Reconfigure GitLab to apply security hardening settings.
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart registry
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| SSL certificate errors | Incorrect certificate path or permissions | Verify certificate paths and run sudo chown root:gitlab-registry /etc/letsencrypt/live/*/ |
| Docker login fails | Authentication configuration missing | Check JWT token settings and run sudo gitlab-ctl reconfigure |
| Push/pull timeouts | Firewall blocking registry port | Ensure port 5050 is open: sudo ufw allow 5050/tcp |
| Registry not starting | Storage directory permissions | Fix ownership: sudo chown git:git /var/opt/gitlab/gitlab-rails/shared/registry |
| 403 Forbidden errors | Missing deploy tokens or permissions | Create deploy token with registry scopes in GitLab project settings |
| Certificate renewal fails | GitLab process locks certificate files | Add post-hook: --post-hook "gitlab-ctl restart registry" |
Next steps
- Install and configure GitLab CE with CI/CD runners and backup automation
- Configure GitLab LDAP authentication and user management with Active Directory integration
- Configure GitLab container registry cleanup policies and storage management
- Implement GitLab CI/CD security scanning for Docker images
- Set up GitLab registry mirror and proxy cache for improved performance
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# GitLab Container Registry SSL Configuration Script
# Usage: ./gitlab-registry-ssl.sh <domain> <email>
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage function
usage() {
echo "Usage: $0 <domain> <email>"
echo "Example: $0 example.com admin@example.com"
exit 1
}
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Check logs above.${NC}"
exit 1
}
trap cleanup ERR
# Check arguments
if [ $# -ne 2 ]; then
usage
fi
DOMAIN="$1"
EMAIL="$2"
REGISTRY_DOMAIN="registry.${DOMAIN}"
# Check if running as root or with sudo
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[ERROR] This script must be run as root or with sudo${NC}"
exit 1
fi
# Detect distribution
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"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
FIREWALL_CMD="firewalld"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
FIREWALL_CMD="firewalld"
;;
*)
echo -e "${RED}[ERROR] Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}[ERROR] Cannot detect distribution${NC}"
exit 1
fi
echo -e "${GREEN}[1/8] Verifying GitLab installation...${NC}"
if ! command -v gitlab-ctl &> /dev/null; then
echo -e "${RED}[ERROR] GitLab not found. Please install GitLab first.${NC}"
exit 1
fi
gitlab-ctl status || {
echo -e "${YELLOW}[WARNING] GitLab services not fully running. Attempting reconfigure...${NC}"
gitlab-ctl reconfigure
}
echo -e "${GREEN}[2/8] Installing SSL certificate dependencies...${NC}"
$PKG_UPDATE
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL certbot openssl docker.io
elif [ "$PKG_MGR" = "dnf" ]; then
$PKG_INSTALL certbot openssl docker podman-docker
else
$PKG_INSTALL certbot openssl docker
fi
echo -e "${GREEN}[3/8] Generating SSL certificate...${NC}"
certbot certonly --standalone \
-d "$REGISTRY_DOMAIN" \
--email "$EMAIL" \
--agree-tos --non-interactive || {
echo -e "${RED}[ERROR] Failed to generate SSL certificate. Ensure DNS is configured and port 80 is accessible.${NC}"
exit 1
}
echo -e "${GREEN}[4/8] Creating registry storage directory...${NC}"
mkdir -p /var/opt/gitlab/gitlab-rails/shared/registry
chown git:git /var/opt/gitlab/gitlab-rails/shared/registry
chmod 755 /var/opt/gitlab/gitlab-rails/shared/registry
echo -e "${GREEN}[5/8] Creating security headers configuration...${NC}"
cat > /etc/gitlab/registry_security_headers.conf << EOF
# Security headers for GitLab registry
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;
# Registry-specific headers
add_header Docker-Content-Digest \$upstream_http_docker_content_digest always;
add_header Docker-Distribution-API-Version registry/2.0 always;
EOF
chmod 644 /etc/gitlab/registry_security_headers.conf
echo -e "${GREEN}[6/8] Configuring GitLab registry settings...${NC}"
# Backup existing config
cp /etc/gitlab/gitlab.rb /etc/gitlab/gitlab.rb.backup.$(date +%Y%m%d_%H%M%S)
# Append registry configuration
cat >> /etc/gitlab/gitlab.rb << EOF
# GitLab Container Registry Configuration
registry_external_url 'https://${REGISTRY_DOMAIN}:5050'
# Registry storage settings
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "${REGISTRY_DOMAIN}"
gitlab_rails['registry_port'] = "5050"
gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
# SSL certificate configuration
registry_nginx['ssl_certificate'] = "/etc/letsencrypt/live/${REGISTRY_DOMAIN}/fullchain.pem"
registry_nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/${REGISTRY_DOMAIN}/privkey.pem"
registry_nginx['ssl_protocols'] = "TLSv1.2 TLSv1.3"
registry_nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384"
# Security headers
registry_nginx['custom_nginx_config'] = "include /etc/gitlab/registry_security_headers.conf;"
# Registry storage cleanup
registry['storage_delete_enabled'] = true
registry['compatibility_schema1_enabled'] = false
EOF
echo -e "${GREEN}[7/8] Setting certificate permissions...${NC}"
# Create GitLab SSL group and add gitlab-www user
if ! getent group gitlab-ssl > /dev/null; then
groupadd gitlab-ssl
fi
usermod -a -G gitlab-ssl git
# Set proper permissions for certificates
chmod 750 /etc/letsencrypt/live
chmod 750 /etc/letsencrypt/archive
chgrp -R gitlab-ssl /etc/letsencrypt/live
chgrp -R gitlab-ssl /etc/letsencrypt/archive
echo -e "${GREEN}[8/8] Configuring firewall rules...${NC}"
if [ "$FIREWALL_CMD" = "ufw" ]; then
ufw allow 5050/tcp comment 'GitLab Registry HTTPS'
ufw --force reload
elif [ "$FIREWALL_CMD" = "firewalld" ]; then
systemctl enable --now firewalld
firewall-cmd --permanent --add-port=5050/tcp
firewall-cmd --reload
fi
echo -e "${YELLOW}[INFO] Reconfiguring GitLab with new settings...${NC}"
gitlab-ctl reconfigure
echo -e "${YELLOW}[INFO] Restarting GitLab services...${NC}"
gitlab-ctl restart
echo -e "${GREEN}[SUCCESS] GitLab Container Registry configured successfully!${NC}"
echo -e "${GREEN}Registry URL: https://${REGISTRY_DOMAIN}:5050${NC}"
echo -e "${YELLOW}[INFO] Verifying configuration...${NC}"
# Wait for services to start
sleep 30
# Verification checks
if gitlab-ctl status | grep -q "down"; then
echo -e "${YELLOW}[WARNING] Some GitLab services are not running. Check with: gitlab-ctl status${NC}"
else
echo -e "${GREEN}[VERIFY] All GitLab services are running${NC}"
fi
if curl -sSf -k "https://${REGISTRY_DOMAIN}:5050/v2/" > /dev/null 2>&1; then
echo -e "${GREEN}[VERIFY] Registry endpoint is accessible${NC}"
else
echo -e "${YELLOW}[WARNING] Registry endpoint may not be accessible yet. Allow a few minutes for startup.${NC}"
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Update DNS to point ${REGISTRY_DOMAIN} to this server"
echo "2. Test registry login: docker login ${REGISTRY_DOMAIN}:5050"
echo "3. Configure automatic certificate renewal with: certbot renew --dry-run"
Review the script before running. Execute with: bash install.sh