Container Registry Integration - Harbor Private Registry
Status: Active
Last Updated: 2026-01-30
Category: Containers - Registry Management
Prerequisites: docker-basics, k0s-installation
Time: 2-3 hours
Tags: harbor, registry, docker, kubernetes, security, image-scanning
Summary
Set up private container registries with Harbor for secure image storage. Learn registry installation, image scanning, access control, integration with k0s, and production registry patterns.
π― What You'll Learn
By the end of this article, you'll be able to:
- β Understand container registries
- β Install Harbor registry
- β Push and pull images
- β Scan images for vulnerabilities
- β Configure access control
- β Integrate with Kubernetes
- β Set up image replication
- β Implement registry best practices
π¦ What is a Container Registry?
The Problem
Public Docker Hub:
# Pull from Docker Hub (public)
docker pull nginx:latest
# Problems:
# β Rate limits (100 pulls/6 hours for anonymous)
# β Images are public
# β No vulnerability scanning
# β No access control
# β Dependency on external service
Issues:
- π Security: Can't host proprietary images
- π Compliance: No audit logs
- π¦ Control: Rate limits and availability
- π Scanning: No vulnerability detection
- π° Cost: Paid plans for private repos
The Solution: Private Registry
Private registry provides:
- π Self-hosted: Full control
- π Private: Proprietary images secure
- π Scanning: Automated vulnerability detection
- π₯ RBAC: Role-based access control
- π Audit: Complete activity logs
- π Replication: Multi-region sync
Harbor vs Alternatives
Harbor:
- β Enterprise-grade features
- β Built-in vulnerability scanning
- β RBAC and policies
- β Web UI
- β Replication
- β Open source
vs Docker Registry:
- Basic, no UI, no scanning
vs GitLab Registry:
- Requires full GitLab installation
vs Nexus/Artifactory:
- More complex, broader scope
π₯ Installing Harbor
Prerequisites
# Docker Compose required
docker --version
docker compose version
# Minimum requirements:
# - 2 CPU cores
# - 4GB RAM
# - 40GB disk
Download Harbor
# Download latest
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-online-installer-v2.10.0.tgz
# Extract
tar xzvf harbor-online-installer-v2.10.0.tgz
cd harbor
# List files
ls
# Output: LICENSE harbor.yml.tmpl install.sh prepare
Configure Harbor
# Copy template
cp harbor.yml.tmpl harbor.yml
# Edit configuration
vim harbor.yml
harbor.yml:
# Hostname (use your domain or IP)
hostname: registry.example.com
# HTTP port
http:
port: 80
# HTTPS (optional, recommended)
https:
port: 443
certificate: /path/to/cert.crt
private_key: /path/to/cert.key
# Harbor admin password
harbor_admin_password: Harbor12345 # Change this!
# Database settings
database:
password: root123
max_idle_conns: 100
max_open_conns: 900
# Data volume
data_volume: /data
# Trivy (vulnerability scanning)
trivy:
ignore_unfixed: false
skip_update: false
insecure: false
# Log
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /var/log/harbor
Install
# Prepare configuration
sudo ./prepare
# Install with Trivy scanner
sudo ./install.sh --with-trivy
# Output:
# [Step 0]: checking if docker is installed ...
# [Step 1]: checking docker-compose is installed ...
# [Step 2]: loading Harbor images ...
# [Step 3]: preparing environment ...
# [Step 4]: preparing harbor configs ...
# [Step 5]: starting Harbor ...
# β ----Harbor has been installed and started successfully.----
Access Harbor
# Open browser
http://registry.example.com
# Login:
# Username: admin
# Password: Harbor12345 (or what you set)
Verify Installation
# Check containers
docker ps
# Output:
# CONTAINER ID IMAGE COMMAND STATUS
# abc123 goharbor/harbor-portal:v2.10.0 "nginx -g 'daemon ofβ¦" Up 2 minutes
# def456 goharbor/harbor-core:v2.10.0 "/harbor/entrypoint.β¦" Up 2 minutes
# ghi789 goharbor/harbor-jobservice:v2.10.0 "/harbor/entrypoint.β¦" Up 2 minutes
# jkl012 goharbor/registry-photon:v2.10.0 "/home/harbor/entrypβ¦" Up 2 minutes
# mno345 goharbor/harbor-db:v2.10.0 "/docker-entrypoint.β¦" Up 2 minutes
# pqr678 goharbor/redis-photon:v2.10.0 "redis-server /etc/rβ¦" Up 2 minutes
πΌοΈ Working with Images
Create Project
In Harbor UI:
- Click Projects β + New Project
- Project Name:
myapp - Access Level: Private
- Click OK
Login to Registry
# Docker login
docker login registry.example.com
# Username: admin
# Password: Harbor12345
# Output: Login Succeeded
Tag and Push Image
# Build image
docker build -t myapp:1.0.0 .
# Tag for Harbor
docker tag myapp:1.0.0 registry.example.com/myapp/myapp:1.0.0
# Push
docker push registry.example.com/myapp/myapp:1.0.0
# Output:
# The push refers to repository [registry.example.com/myapp/myapp]
# 1.0.0: digest: sha256:abc123... size: 1234
Pull Image
# From any machine with access
docker pull registry.example.com/myapp/myapp:1.0.0
# Output:
# 1.0.0: Pulling from myapp/myapp
# Already exists
# Digest: sha256:abc123...
# Status: Downloaded newer image for registry.example.com/myapp/myapp:1.0.0
List Images
In Harbor UI:
- Click project myapp
- See all images
- Click image to see tags
CLI:
# Using Harbor API
curl -u admin:Harbor12345 \
https://registry.example.com/api/v2.0/projects/myapp/repositories
# Output: [{"name":"myapp/myapp","tags_count":1,...}]
π Vulnerability Scanning
Automatic Scanning
Configure project:
- Click Projects β myapp
- Click Configuration tab
- Enable Automatically scan images on push
- Click Save
Now every push triggers scan!
Manual Scan
In Harbor UI:
- Navigate to image
- Click Scan button
- Wait for completion (30s - 2min)
- View results
View Vulnerabilities
After scan:
Image: myapp:1.0.0
Vulnerabilities: 12
ββ Critical: 2
ββ High: 3
ββ Medium: 5
ββ Low: 2
Details:
CVE-2023-1234 | Critical | openssl | 1.1.1k β 1.1.1n
CVE-2023-5678 | High | curl | 7.68.0 β 7.81.0
Click CVE to see:
- Description
- Severity
- Fixed version
- Links to security advisories
Prevent Vulnerable Images
Set policy:
- Project Configuration
- Prevent vulnerable images from running
- Severity: Critical or High
- Save
Now k8s can't pull images with critical/high CVEs!
π₯ Access Control
Create Users
In Harbor UI:
- Click Users β + New User
- Username:
developer - Email:
dev@example.com - Set password
- Click OK
Project Members
Add to project:
- Click Projects β myapp
- Members tab
- + User β Select
developer - Role: Developer
- OK
Roles
Project roles:
- π Project Admin: Full control
- π¨βπ» Developer: Push/pull images
- π Guest: Read-only access
- π§ Maintainer: Manage project settings
Robot Accounts
For CI/CD:
- Project β Robot Accounts tab
- + New Robot Account
- Name:
ci-bot - Expiration: 365 days (or never)
- Permissions: Push and Pull
- Add
- Copy token (shown once!)
Use in CI:
docker login registry.example.com \
-u 'robot$ci-bot' \
-p 'eyJhbGciOiJSUzI1...'
π Kubernetes Integration
Create Secret
# Create docker-registry secret
kubectl create secret docker-registry harbor-registry \
--docker-server=registry.example.com \
--docker-username=admin \
--docker-password=Harbor12345 \
--docker-email=admin@example.com
# Verify
kubectl get secret harbor-registry
Use in Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
# Reference secret
imagePullSecrets:
- name: harbor-registry
containers:
- name: app
# Use private image
image: registry.example.com/myapp/myapp:1.0.0
ports:
- containerPort: 8080
kubectl apply -f deployment.yaml
# Check pods
kubectl get pods
# Output:
# NAME READY STATUS RESTARTS AGE
# myapp-abc123-xyz 1/1 Running 0 30s
# myapp-def456-abc 1/1 Running 0 30s
# myapp-ghi789-def 1/1 Running 0 30s
Default Service Account
Add to default service account (applies to all pods):
kubectl patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "harbor-registry"}]}'
# Now all deployments automatically use this secret
π Image Replication
Setup Replication
For disaster recovery or multi-region:
Harbor 1 (Source):
- Administration β Registries
- + New Endpoint
- Provider: Harbor
- Name:
harbor-backup - Endpoint URL:
https://harbor2.example.com - Access ID:
admin - Access Secret:
password - Test Connection β OK
Create replication rule:
- Administration β Replications
- + New Replication Rule
- Name:
myapp-replication - Replication mode: Push-based
- Source resource filter:
myapp/** - Destination registry:
harbor-backup - Trigger Mode: Event Based (on push)
- Save
Now images automatically replicate!
Manual Replication
# Trigger manually
# Harbor UI β Replications β Select rule β Replicate
π― Complete Example
CI/CD Pipeline
GitLab CI/CD (.gitlab-ci.yml):
stages:
- build
- scan
- deploy
variables:
REGISTRY: registry.example.com
IMAGE_NAME: $REGISTRY/myapp/myapp
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
# Login to Harbor
- echo "$HARBOR_PASSWORD" | docker login $REGISTRY -u "$HARBOR_USERNAME" --password-stdin
# Build image
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
# Push to Harbor
- docker push $IMAGE_NAME:$IMAGE_TAG
- docker push $IMAGE_NAME:latest
scan:
stage: scan
image: curlimages/curl:latest
script:
# Trigger scan via Harbor API
- |
curl -X POST \
-u "$HARBOR_USERNAME:$HARBOR_PASSWORD" \
"$REGISTRY/api/v2.0/projects/myapp/repositories/myapp/artifacts/$IMAGE_TAG/scan"
# Wait for scan to complete
- sleep 30
# Get scan results
- |
SCAN_RESULT=$(curl -s \
-u "$HARBOR_USERNAME:$HARBOR_PASSWORD" \
"$REGISTRY/api/v2.0/projects/myapp/repositories/myapp/artifacts/$IMAGE_TAG")
echo "$SCAN_RESULT" | jq '.scan_overview'
# Fail if critical vulnerabilities
CRITICAL=$(echo "$SCAN_RESULT" | jq '.scan_overview."application/vnd.security.vulnerability.report; version=1.1".summary.critical')
if [ "$CRITICAL" -gt "0" ]; then
echo "Critical vulnerabilities found!"
exit 1
fi
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
# Update deployment with new image
- kubectl set image deployment/myapp app=$IMAGE_NAME:$IMAGE_TAG
- kubectl rollout status deployment/myapp
GitHub Actions
.github/workflows/deploy.yml:
name: Build and Deploy
on:
push:
branches: [main]
env:
REGISTRY: registry.example.com
IMAGE_NAME: myapp/myapp
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Harbor
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Scan image
run: |
# Wait for Harbor to process
sleep 10
# Check vulnerabilities
curl -u "${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }}" \
"${{ env.REGISTRY }}/api/v2.0/projects/myapp/repositories/myapp/artifacts/${{ github.sha }}" \
| jq '.scan_overview'
- name: Deploy to k8s
run: |
kubectl set image deployment/myapp \
app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
π SSL/TLS Configuration
Generate Certificate
Using Let's Encrypt:
# Install certbot
sudo apt-get install certbot
# Generate certificate
sudo certbot certonly --standalone -d registry.example.com
# Certificates at:
# /etc/letsencrypt/live/registry.example.com/fullchain.pem
# /etc/letsencrypt/live/registry.example.com/privkey.pem
Configure Harbor
Edit harbor.yml:
https:
port: 443
certificate: /etc/letsencrypt/live/registry.example.com/fullchain.pem
private_key: /etc/letsencrypt/live/registry.example.com/privkey.pem
Restart Harbor:
cd harbor
sudo docker compose down
sudo ./prepare
sudo docker compose up -d
Update Docker Clients
# Copy CA certificate
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp /etc/letsencrypt/live/registry.example.com/fullchain.pem \
/etc/docker/certs.d/registry.example.com/ca.crt
# Test
docker login registry.example.com
π Troubleshooting
Cannot Push Images
Check authentication:
# Verify login
docker login registry.example.com
# Check credentials
cat ~/.docker/config.json
# Verify network
curl -I https://registry.example.com
Kubernetes Can't Pull
Check secret:
# Verify secret exists
kubectl get secret harbor-registry
# View secret details
kubectl get secret harbor-registry -o yaml
# Check pod events
kubectl describe pod <pod-name>
# Look for ImagePullBackOff error
# Common issues:
# - Wrong credentials
# - Missing imagePullSecrets
# - Network can't reach registry
Scan Fails
Check Trivy:
# Check Trivy container
docker ps | grep trivy
# View Trivy logs
docker logs harbor-trivy
# Common issues:
# - Database update failed
# - Network issues downloading CVE data
# - Insufficient disk space
Harbor UI Not Accessible
Check containers:
# All should be Up
docker ps -a | grep harbor
# Check logs
docker logs harbor-core
docker logs harbor-portal
# Check ports
sudo netstat -tlnp | grep :80
sudo netstat -tlnp | grep :443
π‘ Best Practices
1. Use Robot Accounts
# For CI/CD, create robot accounts
# Don't use admin credentials in pipelines
Better security and audit trail.
2. Enable Scanning
# Project Configuration
automatically_scan_images_on_push: true
prevent_vulnerable_images: true
severity_level: High
Catch vulnerabilities early.
3. Tag Images Properly
# Good: Semantic versioning + commit hash
registry.example.com/myapp/api:1.2.3-abc123def
# Also tag with branch
registry.example.com/myapp/api:main
# Bad: Only 'latest'
registry.example.com/myapp/api:latest
4. Quota Management
Set project quotas:
- Project β Configuration
- Project Quota: 100GB
- Save
Prevents runaway storage usage.
5. Regular Backups
# Backup Harbor data
cd harbor
sudo docker compose stop
# Backup data directory
sudo tar -czf harbor-backup-$(date +%Y%m%d).tar.gz /data
# Backup database
docker exec harbor-db pg_dumpall -U postgres > harbor-db-$(date +%Y%m%d).sql
sudo docker compose start
π What's Next?
CI/CD:
- ../cicd/gitlab-ci-basics - Integrate with pipelines
Kubernetes:
- k0s-ingress - Expose applications
Security:
- ../security/container-security - Harden containers
π Resources
Harbor:
Container Security:
π Change Log
2026-01-30
- Created Harbor guide
- Explained registry concepts
- Covered installation
- Demonstrated image operations
- Showed vulnerability scanning
- Implemented access control
- Kubernetes integration
- Complete CI/CD example
- SSL/TLS configuration
- Troubleshooting guide
- Best practices
Next Article: kubernetes-concepts - Core Kubernetes objects!