Deploy Harbor Private Registry

· 6 min read
Deploy Harbor Private Registry

Harbor is deployed as several Docker containers. You can therefore deploy it on any Linux distribution that supports Docker. The target host requires Docker, and Docker Compose to be installed.

Prerequisites

Hardware

The following table lists the minimum and recommended hardware configurations for deploying Harbor.

Resource Minimum Recommended
CPU 2 CPU 4 CPU
Memory 4 GB 8 GB
Disk 40 GB 160 GB

Software

The following table lists the software versions that must be installed on the target host.

Software Version Description
Docker engine Version 17.06.0-ce+ or higher For installation instructions, see Docker Engine documentation
Docker Compose Version 1.18.0 or higher For installation instructions, see Docker Compose documentation
Openssl Latest is preferred Used to generate certificate and keys for Harbor

Network Ports

Harbor requires that the following ports be open on the target host.

Port Protocol Description
443 HTTPS Harbor portal and core API accept HTTPS requests on this port. You can change this port in the configuration file.
80 HTTP Harbor portal and core API accept HTTP requests on this port. You can change this port in the configuration file.

Launch an EC2 Instance

  1. AWS Account and Login
    • If you don’t have an AWS account, sign up at AWS Sign Up.
    • Log in to the AWS Management Console.
  1. Navigate to EC2 Dashboard
  2. Choose an Amazon Machine Image (AMI)
    • Click "Launch Instance."
    • In the AMI selection screen, search for "Ubuntu 24.04".
  1. Choose an Instance Type
    • Select the t3.medium instance type.
    • Click "Next: Configure Instance Details."
  1. Select/Create a Key Pair
    • Select "Create a new key pair."
    • Name the key pair.
    • Download the key pair file (.pem) and store it securely.
  1. Network Settings
    • Use the default settings.
    • Ensure you have selected the correct VPC and subnet.
  1. Configure Security Group
    • Create a new security group.
    • Set the security group name and description.
    • Add a rule to allow SSH, HTTP, & HTTPS Traffic:
      • SSH
        • Type: ssh
        • Protocol: TCP
        • Port Range : 22
        • Source: Anywhere
      • HTTP
        • Type: HTTP
        • Protocol: TCP
        • Port Range : 80
        • Source: Anywhere
      • HTTPS
        • Type: HTTPS
        • Protocol: TCP
        • Port Range : 443
        • Source: Anywhere
  1. Add Storage
    • Modify the root volume size to 40 GB.
    • Add Second volume with the size 40 GB.
  1. Review Instance Launch
    • Review all configurations.
    • Click "Launch."

Install Docker

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Setup Second Disk

  1. Create Partition
lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0          7:0    0 25.2M  1 loop /snap/amazon-ssm-agent/7993
loop1          7:1    0 55.7M  1 loop /snap/core18/2829
loop2          7:2    0 38.8M  1 loop /snap/snapd/21759
nvme0n1      259:0    0   40G  0 disk
├─nvme0n1p1  259:2    0   39G  0 part /
├─nvme0n1p14 259:3    0    4M  0 part
├─nvme0n1p15 259:4    0  106M  0 part /boot/efi
└─nvme0n1p16 259:5    0  913M  0 part /boot
nvme1n1      259:1    0   40G  0 disk

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1):
First sector (2048-83886079, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-83886079, default 83886079):

Created a new partition 1 of type 'Linux' and of size 40 GiB.

Command (m for help): t
Selected partition 1
Hex code or alias (type L to list all): 8e
Changed type of partition 'Linux' to 'Linux LVM'.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0          7:0    0 25.2M  1 loop /snap/amazon-ssm-agent/7993
loop1          7:1    0 55.7M  1 loop /snap/core18/2829
loop2          7:2    0 38.8M  1 loop /snap/snapd/21759
nvme0n1      259:0    0   40G  0 disk
├─nvme0n1p1  259:2    0   39G  0 part /
├─nvme0n1p14 259:3    0    4M  0 part
├─nvme0n1p15 259:4    0  106M  0 part /boot/efi
└─nvme0n1p16 259:5    0  913M  0 part /boot
nvme1n1      259:1    0   40G  0 disk
└─nvme1n1p1  259:6    0   40G  0 part
  1. Create LVM
# Create PV 
pvcreate /dev/nvme1n1p1
 
# Create VG 
vgcreate harbor_vg /dev/nvme1n1p1 
 
# Create LV 
lvcreate -n harbor_lv -l 100%FREE harbor_vg 

# Format LVM to XFS 
mkfs.xfs /dev/mapper/harbor_vg-harbor_lv
  1. Create & Mount Directory
mkdir /data
mount -t xfs /dev/mapper/harbor_vg-harbor_lv /data/ 
nano /etc/fstab
---
/dev/mapper/harbor_vg-harbor_lv   /data      xfs    defaults        0 0 
---
systemctl daemon-reload
mount -a 
df -hT
lsblk
NAME                    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0                     7:0    0 25.2M  1 loop /snap/amazon-ssm-agent/7993
loop1                     7:1    0 55.7M  1 loop /snap/core18/2829
loop2                     7:2    0 38.8M  1 loop /snap/snapd/21759
nvme0n1                 259:0    0   40G  0 disk
├─nvme0n1p1             259:2    0   39G  0 part /
├─nvme0n1p14            259:3    0    4M  0 part
├─nvme0n1p15            259:4    0  106M  0 part /boot/efi
└─nvme0n1p16            259:5    0  913M  0 part /boot
nvme1n1                 259:1    0   40G  0 disk
└─nvme1n1p1             259:6    0   40G  0 part
  └─harbor_vg-harbor_lv 252:0    0   40G  0 lvm  /data

Deploy Harbor

# Download Harbor Installer
wget https://github.com/goharbor/harbor/releases/download/v2.11.0/harbor-online-installer-v2.11.0.tgz
tar xvfz harbor-online-installer-v2.11.0.tgz
cd harbor/
# Edit harbor.yml
cp harbor.yml.tmpl harbor.yml
nano harbor.yml
---
hostname: registry.yourdomain.com
http:
  port: 80

https:
  port: 443
  certificate: /your/certificate/path
  private_key: /your/private/key/path

harbor_admin_password: StR0N9p@S5WORD!

database:
  password: StR0N9p@S5WORD!

data_volume: /data
# Run harbor prepare with trivy
./prepare --with-trivy
# Start harbor service with docker compose
docker compose up -d

Operasional Test Pull and Push Image

docker login registry.yourdomain.com

docker pull nginx:alpine
docker tag nginx:alpine registry.yourdomain.com/library/nginx:alpine
docker push registry.yourdomain.com/library/nginx:alpine

Trust Private SSL Certificate?

Here's an example workaround if you are using a private SSL certificate for Harbor where you need to manually trust the certificate.

  1. Linux
cat /your/ca-certificate/path >> /etc/ssl/certs/ca-certificates.crt

curl https://registry.yourdomain.com
  1. Docker
mkdir -p /etc/docker/certs.d/registry.yourdomain.com/
cp ca.crt /etc/docker/certs.d/registry.yourdomain.com/
systemctl restart docker

docker login registry.yourdomain.com
  1. Containerd
cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo systemctl restart containerd
  1. Cri-o
nano /etc/crio/crio.conf
---
[crio.image]
insecure_registries = [ "registry.yourdomain.com"
]
---

nano /etc/crio/crio.conf.d/registries.conf
---
# insecure_registries is used to skip TLS verification when pulling images.
insecure_registries = [
 "https://registry.yourdomain.com"
]

# registries is used to specify a comma separated list of registries to be used
# when pulling an unqualified image (e.g. fedora:rawhide).
registries = [
 "docker.io",
 "https://registry.yourdomain.com"
]
---

crictl pull --creds "admin:StR0N9p@S5WORD!" registry.yourdomain.com/library/nginx:alpine
  1. RKE2
nano /etc/rancher/rke2/registries.yaml
---
mirrors:
  registry.yourdomain.com:
    endpoint:
      - "https://registry.yourdomain.com"
configs:
  "registry.yourdomain.com":
    tls:
      cert_file: "/etc/ssl/private/registry.yourdomain.com/registry.yourdomain.com.crt"
      key_file: "/etc/ssl/private/registry.yourdomain.com/registry.yourdomain.com.key"
      ca_file: "/etc/ssl/private/registry.yourdomain.com/ca.crt"
      insecure_skip_verify: true
---
# For master nodes
systemctl restart rke2-server

# For worker and ingress nodes
systemctl restart rke2-agent
cat << EOF > test-pull.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-harbor
spec:
  containers:
  - name: test-harbor-nginx
    image: registry.yourdomain.com/nginx:alpine
EOF

kubectl apply -f test-pull.yaml

Renew Certificate

  1. Get your new certificate
  2. Stop Harbor: sudo docker-compose down
  3. Edit harbor.yml, replace your old certificate w new certificate
  4. Re-run prepare command and redeploy harbor: ./prepare --with-trivy
  5. Redeploy Harbor: docker-compose up -d