Harbor is an enterprise-grade, open-source container registry that extends the base OCI registry specification with role-based access control, project isolation, image vulnerability scanning powered by Trivy, and cross-datacenter image replication. Running your own Harbor instance on RHEL 8 gives your team a private, auditable registry with no bandwidth costs or rate-limiting imposed by Docker Hub. In this tutorial you will install Docker and Docker Compose on RHEL 8, deploy Harbor using its official offline installer, configure Trivy vulnerability scanning, and push and pull images from your private registry.
Prerequisites
- RHEL 8 server with at least 4 CPU cores and 8 GB RAM (Harbor recommendation)
- 40 GB+ free disk space for image storage
- sudo privileges
- A hostname or IP address for the Harbor server (used in
harbor.yml) - Port 80 (and optionally 443) open in the firewall
- Docker CE and Docker Compose installed (step 1)
Step 1 — Install Docker CE and Docker Compose on RHEL 8
Harbor’s installer script relies on Docker CE and the Docker Compose plugin. Add the official Docker repository and install both.
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
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version
Step 2 — Download the Harbor Offline Installer
Harbor provides an offline installer that bundles all required Docker images into a single tarball — no internet access is required during installation, making it suitable for air-gapped environments.
# Check the latest release at https://github.com/goharbor/harbor/releases
HARBOR_VERSION="v2.10.2"
cd /tmp
curl -LO "https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-offline-installer-${HARBOR_VERSION}.tgz"
curl -LO "https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-offline-installer-${HARBOR_VERSION}.tgz.asc"
# Verify the checksum
sha256sum harbor-offline-installer-${HARBOR_VERSION}.tgz
# Extract to /opt
sudo tar xzf harbor-offline-installer-${HARBOR_VERSION}.tgz -C /opt
ls /opt/harbor/
Step 3 — Configure harbor.yml
Harbor reads all configuration from harbor.yml. At minimum you must set the hostname and the initial admin password. For HTTP-only access (lab/internal use), comment out the https block.
cd /opt/harbor
sudo cp harbor.yml.tmpl harbor.yml
sudo vi harbor.yml
# Key settings to edit in harbor.yml:
#
# hostname: YOUR_SERVER_IP (or your domain name)
#
# # Comment out the entire https block for HTTP-only:
# # https:
# # port: 443
# # certificate: /your/certificate/path
# # private_key: /your/private/key/path
#
# harbor_admin_password: YourStrongPasswordHere
#
# database:
# password: YourDBPasswordHere
#
# data_volume: /data
# Create the data directory
sudo mkdir -p /data
Step 4 — Run the Installer with Trivy Vulnerability Scanning
The install.sh script generates a docker-compose.yml from your harbor.yml and starts all Harbor services. Pass --with-trivy to include the integrated vulnerability scanner.
cd /opt/harbor
sudo ./install.sh --with-trivy
# The installer will:
# 1. Validate harbor.yml
# 2. Generate docker-compose.yml
# 3. Load all images from the offline tarball
# 4. Start Harbor containers
# Verify all containers are healthy
docker compose ps
# Expected output — all services should show "healthy" or "running":
# NAME STATUS
# harbor-core Up (healthy)
# harbor-db Up (healthy)
# harbor-jobservice Up
# harbor-log Up
# harbor-portal Up
# harbor-redis Up
# harbor-registryctl Up
# nginx Up
# registry Up
# trivy-adapter Up
Open http://YOUR_SERVER_IP in a browser. Log in with username admin and the password set in harbor.yml.
Step 5 — Create a Project, Push an Image, and Scan for Vulnerabilities
Harbor organizes images into projects. Create a project in the UI or via the API, then use standard Docker commands to tag, push, and scan images.
# Log in to your Harbor registry
docker login YOUR_SERVER_IP
# Enter: admin / YourStrongPasswordHere
# Pull a public image to test with
docker pull nginx:alpine
# Tag it for your Harbor project
# Format: REGISTRY/PROJECT/IMAGE:TAG
docker tag nginx:alpine YOUR_SERVER_IP/library/nginx:alpine
# Push the image to Harbor
docker push YOUR_SERVER_IP/library/nginx:alpine
# Trigger a Trivy vulnerability scan via the Harbor API
curl -u admin:YourStrongPasswordHere
-X POST
"http://YOUR_SERVER_IP/api/v2.0/projects/library/repositories/nginx/artifacts/alpine/scan"
-H "Content-Type: application/json"
# Check the scan results (wait ~60 seconds for Trivy to finish)
curl -s -u admin:YourStrongPasswordHere
"http://YOUR_SERVER_IP/api/v2.0/projects/library/repositories/nginx/artifacts/alpine?with_scan_overview=true"
| python3 -m json.tool | grep -A5 "scan_overview"
Step 6 — Configure Image Replication to a Remote Registry
Harbor’s replication rules automatically mirror images to or from remote registries such as Docker Hub, Quay.io, or another Harbor instance. This is useful for syncing approved golden images to edge locations.
# Create a replication endpoint via the Harbor API
curl -s -u admin:YourStrongPasswordHere
-X POST
"http://YOUR_SERVER_IP/api/v2.0/registries"
-H "Content-Type: application/json"
-d '{
"name": "docker-hub",
"type": "docker-hub",
"url": "https://hub.docker.com",
"credential": {
"type": "basic",
"access_key": "YOUR_DOCKERHUB_USERNAME",
"access_secret": "YOUR_DOCKERHUB_TOKEN"
}
}'
# List registered replication endpoints
curl -s -u admin:YourStrongPasswordHere
"http://YOUR_SERVER_IP/api/v2.0/registries" | python3 -m json.tool
# To manage users and projects via CLI
curl -s -u admin:YourStrongPasswordHere
-X POST
"http://YOUR_SERVER_IP/api/v2.0/projects"
-H "Content-Type: application/json"
-d '{"project_name":"myteam","public":false}'
Conclusion
You have deployed a fully functional Harbor container registry on RHEL 8 with integrated Trivy vulnerability scanning and image replication capabilities. Your team now has a private, auditable registry that enforces project-level access control, automatically scans every pushed image for known CVEs, and can synchronize images to remote registries without manual intervention. Pointing your CI/CD pipelines at YOUR_SERVER_IP instead of Docker Hub eliminates rate-limiting, reduces external bandwidth, and keeps all image artifacts within your network boundary.
Next steps: Securing Harbor with a Let’s Encrypt TLS Certificate on RHEL 8, Enforcing Vulnerability Scanning Policies to Block Critical CVEs in Harbor, and Integrating Harbor with Kubernetes ImagePullSecrets for Automated Image Pulls.