How to Set Up a Local Container Registry with Harbor on RHEL 7

Harbor is a CNCF-graduated open-source container registry that extends a plain Docker registry with enterprise features: role-based access control (RBAC), replication policies, vulnerability scanning, content trust, and a web-based management UI. Running Harbor on your own RHEL 7 infrastructure means your container images never leave your network, you control retention and access policies, and you are not subject to the rate limits or pricing changes of public registries. This tutorial covers downloading the Harbor offline installer, configuring harbor.yml for TLS, running the installation script, creating projects and users through the UI and CLI, setting up replication rules, and enabling vulnerability scanning with Trivy.

Prerequisites

  • RHEL 7 server with at least 4 CPU cores and 8 GB RAM
  • At least 40 GB of free disk space for image storage
  • Docker CE installed and running
  • Docker Compose 1.18.0 or later installed
  • A domain name or IP address for the Harbor host (used in TLS certificate generation)
  • Ports 80, 443, and 4443 reachable through firewalld
  • Root or sudo access

Step 1: Install Docker and Docker Compose on RHEL 7

If Docker CE is not already installed, set up the Docker repository and install it:

sudo yum install -y yum-utils device-mapper-persistent-data lvm2

sudo yum-config-manager --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install -y docker-ce docker-ce-cli containerd.io

sudo systemctl enable docker
sudo systemctl start docker

# Install Docker Compose
sudo curl -L 
    "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64" 
    -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Verify
docker --version
docker-compose --version

Step 2: Download the Harbor Offline Installer

Harbor provides two installer types: online (downloads images at install time) and offline (bundles all images in the tarball). The offline installer is recommended for air-gapped environments and ensures a reproducible installation.

# Set the desired Harbor version
HARBOR_VERSION="2.9.1"

# Download the offline installer
curl -fsSL 
    "https://github.com/goharbor/harbor/releases/download/v${HARBOR_VERSION}/harbor-offline-installer-v${HARBOR_VERSION}.tgz" 
    -o /tmp/harbor-offline-installer.tgz

# Download and verify the checksum
curl -fsSL 
    "https://github.com/goharbor/harbor/releases/download/v${HARBOR_VERSION}/harbor-offline-installer-v${HARBOR_VERSION}.tgz.sha256sum" 
    -o /tmp/harbor.sha256sum
sha256sum -c /tmp/harbor.sha256sum

# Extract to /opt
sudo tar -xzf /tmp/harbor-offline-installer.tgz -C /opt
ls /opt/harbor/

You should see files including harbor.yml.tmpl, install.sh, prepare, and common.sh.

Step 3: Generate a TLS Certificate for Harbor

Harbor requires HTTPS in production. For a lab or internal deployment, generate a self-signed certificate. For a production deployment, use a certificate from Let’s Encrypt or your internal CA.

HARBOR_HOSTNAME="harbor.example.com"
CERT_DIR="/opt/harbor/certs"
sudo mkdir -p $CERT_DIR

# Generate a CA private key
sudo openssl genrsa -out $CERT_DIR/ca.key 4096

# Generate the CA certificate
sudo openssl req -x509 -new -nodes 
    -key $CERT_DIR/ca.key 
    -sha256 -days 1825 
    -out $CERT_DIR/ca.crt 
    -subj "/C=US/ST=State/L=City/O=MyOrg/CN=Harbor CA"

# Generate the server private key
sudo openssl genrsa -out $CERT_DIR/harbor.key 4096

# Generate a Certificate Signing Request
sudo openssl req -new 
    -key $CERT_DIR/harbor.key 
    -out $CERT_DIR/harbor.csr 
    -subj "/C=US/ST=State/L=City/O=MyOrg/CN=${HARBOR_HOSTNAME}"

# Create a v3 extension file for SAN
cat > /tmp/harbor_v3.ext <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${HARBOR_HOSTNAME}
IP.1  = 192.168.1.100
EOF

# Sign the certificate with the CA
sudo openssl x509 -req 
    -in $CERT_DIR/harbor.csr 
    -CA $CERT_DIR/ca.crt 
    -CAkey $CERT_DIR/ca.key 
    -CAcreateserial 
    -out $CERT_DIR/harbor.crt 
    -days 730 
    -sha256 
    -extfile /tmp/harbor_v3.ext

Step 4: Configure harbor.yml

Copy the template configuration file and edit it for your environment:

sudo cp /opt/harbor/harbor.yml.tmpl /opt/harbor/harbor.yml
sudo vi /opt/harbor/harbor.yml

Key sections to configure:

# /opt/harbor/harbor.yml (relevant sections)

# The FQDN or IP address of the Harbor host
hostname: harbor.example.com

# HTTP and HTTPS settings
http:
  port: 80

https:
  port: 443
  certificate: /opt/harbor/certs/harbor.crt
  private_key: /opt/harbor/certs/harbor.key

# Harbor admin password (change immediately after first login)
harbor_admin_password: ChangeMe!Str0ng

# Database settings (uses embedded PostgreSQL by default)
database:
  password: ChangeDBPassword!
  max_idle_conns: 50
  max_open_conns: 1000

# Default data volume for image storage
data_volume: /opt/harbor/data

# Job service settings (log retention, etc.)
jobservice:
  max_job_workers: 10

# Log settings
log:
  level: info
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor

Step 5: Run the Installation Script

Harbor’s prepare script generates configuration files and the Docker Compose file from harbor.yml. The install.sh script then starts all Harbor services.

cd /opt/harbor

# Generate configuration (runs the prepare script internally)
# --with-trivy enables the Trivy vulnerability scanner
# --with-chartmuseum enables Helm chart hosting (optional)
sudo ./install.sh --with-trivy

# Monitor the startup
docker-compose -f /opt/harbor/docker-compose.yml ps

Successful output should show services like harbor-core, harbor-db, harbor-jobservice, harbor-log, harbor-portal, harbor-redis, nginx, registry, registryctl, and trivy-adapter all in Up (healthy) state.

Step 6: Open Firewall Ports on RHEL 7

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=4443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Step 7: Log In and Configure Docker to Trust the Certificate

For clients using a self-signed certificate, Docker must be configured to trust your Harbor CA:

# On each Docker client machine (including the Harbor server itself):
HARBOR_HOSTNAME="harbor.example.com"
sudo mkdir -p /etc/docker/certs.d/${HARBOR_HOSTNAME}

# Copy the CA certificate (or the harbor.crt if no separate CA)
sudo cp /opt/harbor/certs/ca.crt 
    /etc/docker/certs.d/${HARBOR_HOSTNAME}/ca.crt

# Also install for system-wide trust on RHEL 7
sudo cp /opt/harbor/certs/ca.crt /etc/pki/ca-trust/source/anchors/harbor-ca.crt
sudo update-ca-trust

# Log in to Harbor
docker login harbor.example.com
# Username: admin
# Password: ChangeMe!Str0ng

Step 8: Creating Projects and Users

Harbor organizes images into projects. Each project can be public (no authentication required to pull) or private. Navigate to https://harbor.example.com, log in as admin, and use the web UI or the Harbor CLI (harbor-cli) to create resources.

# Using the Harbor HTTP API (v2) to create a project via curl:
curl -u admin:ChangeMe!Str0ng 
     -X POST "https://harbor.example.com/api/v2.0/projects" 
     -H "Content-Type: application/json" 
     -d '{
           "project_name": "production",
           "public": false,
           "metadata": {"auto_scan": "true"}
         }'

# Create a user
curl -u admin:ChangeMe!Str0ng 
     -X POST "https://harbor.example.com/api/v2.0/users" 
     -H "Content-Type: application/json" 
     -d '{
           "username": "devops-user",
           "email": "[email protected]",
           "password": "DevOpsPass!23",
           "realname": "DevOps User",
           "comment": "CI/CD service account"
         }'

# Add user to project with developer role (role_id: 2=maintainer, 3=developer, 4=guest)
PROJECT_ID=2
curl -u admin:ChangeMe!Str0ng 
     -X POST "https://harbor.example.com/api/v2.0/projects/${PROJECT_ID}/members" 
     -H "Content-Type: application/json" 
     -d '{"role_id": 3, "member_user": {"username": "devops-user"}}'

Step 9: Pushing and Pulling Images

# Pull a public image and push to Harbor
docker pull nginx:alpine
docker tag nginx:alpine harbor.example.com/production/nginx:alpine
docker push harbor.example.com/production/nginx:alpine

# Pull from Harbor
docker pull harbor.example.com/production/nginx:alpine

# Podman also works with Harbor
podman login harbor.example.com
podman push harbor.example.com/production/myapp:1.0.0

Step 10: Replication Rules

Harbor replication allows you to synchronize images between Harbor instances or between Harbor and external registries such as Docker Hub, Quay.io, or AWS ECR. Replication can be push-based (Harbor pushes to a remote target) or pull-based (Harbor pulls from a remote source).

In the Harbor UI: Administration → Replications → New Replication Rule. Configure the replication endpoint, filter (by repository name pattern, tag, or label), trigger (manual, scheduled, or event-based), and destination namespace.

# Example: replicate all images tagged 'stable' from Docker Hub to Harbor
# (configured via the API)
curl -u admin:ChangeMe!Str0ng 
     -X POST "https://harbor.example.com/api/v2.0/replication/policies" 
     -H "Content-Type: application/json" 
     -d '{
           "name": "pull-from-dockerhub",
           "src_registry": {"id": 1},
           "dest_namespace": "mirror",
           "filters": [{"type": "tag", "value": "stable"}],
           "trigger": {"type": "scheduled", "trigger_settings": {"cron": "0 0 2 * * *"}},
           "deletion": false,
           "override": true,
           "enabled": true
         }'

Step 11: Vulnerability Scanning with Trivy

When Harbor is installed with --with-trivy, every pushed image can be automatically scanned for known CVEs against the Trivy vulnerability database. Enable auto-scan at the project level so all new pushes are scanned immediately.

# Trigger a manual scan of a specific artifact via API
curl -u admin:ChangeMe!Str0ng 
     -X POST "https://harbor.example.com/api/v2.0/projects/production/repositories/nginx/artifacts/alpine/scan"

# View scan results
curl -u admin:ChangeMe!Str0ng 
     "https://harbor.example.com/api/v2.0/projects/production/repositories/nginx/artifacts/alpine/additions/vulnerabilities" 
     | python -m json.tool | head -80

You can also configure a scan-on-push policy at the system level (Administration → Interrogation Services → Configure) to scan all newly pushed images automatically. Combine this with a prevent vulnerable images from running policy (Project → Configuration → Prevent vulnerable images from running) to block images with Critical or High CVEs from being pulled.

Conclusion

Harbor transforms a RHEL 7 server into a full-featured, enterprise-grade container registry with minimal effort. The offline installer, combined with Docker Compose, makes Harbor straightforward to deploy and upgrade. TLS encryption, RBAC with granular project-level roles, automated vulnerability scanning via Trivy, and cross-registry replication give operations teams the governance and security controls required in regulated environments. Once Harbor is running, integrate it into your Drone or Jenkins CI pipelines so that every successful build automatically pushes a scanned, tagged image to your private registry — and your Kubernetes or Docker Swarm deployments pull exclusively from Harbor, ensuring that only vetted images run in production.