How to Install Harbor Private Container Registry

install harbor private container registry

Harbor is a CNCF-graduated, open-source container registry that extends the base OCI Distribution spec with vulnerability scanning, image signing, role-based access control, replication, and a full web UI. It’s the go-to choice for teams that need a private, self-hosted alternative to Docker Hub or a managed cloud registry. This guide walks through installing Harbor v2.15.2 — the current stable release — on Ubuntu 26.04 LTS using the official offline installer, with HTTPS, Trivy vulnerability scanning, and a working docker push/pull flow by the end.

By the end of this tutorial you’ll have Harbor running behind TLS, an admin account you can log into, a project created, and a locally built image successfully pushed to your own registry.

Table of Contents

  1. Prerequisites
  2. Installation Methods Compared
  3. Architecture: How Harbor’s Components Fit Together
  4. Step 1: Install Docker and Docker Compose
  5. Step 2: Download the Harbor Installer
  6. Step 3: Generate TLS Certificates
  7. Step 4: Configure harbor.yml
  8. Step 5: Run the Installer
  9. Verifying the Installation
  10. Logging Into the Harbor UI
  11. Creating a Project and Pushing Your First Image
  12. Enabling Vulnerability Scanning with Trivy
  13. Managing Harbor as a systemd Service
  14. Troubleshooting
  15. Uninstalling Harbor
  16. FAQ
  17. Related Reads on bckinfo.com

Prerequisites

Before installing Harbor, make sure your Ubuntu 26.04 LTS host meets the following requirements:

  • A user account with sudo privileges
  • Docker Engine 20.10.10-ce or newer and Docker Compose 1.18.0 or newer (the docker compose plugin satisfies this)
  • Minimum 2 CPU / 4 GB RAM / 40 GB disk for evaluation; 4 CPU / 8 GB RAM / 160 GB disk recommended for production use
  • A resolvable hostname or static IP for the registry (used in TLS certificates and Docker daemon configuration)
  • openssl for generating self-signed certificates, or an existing certificate from your organization’s CA
  • The following ports open on the host: 443 (HTTPS), 4443 (Notary, if enabled), and 80 (HTTP redirect, optional)

Installation Methods Compared

| Method | Best For | Version Freshness | GitOps Friendly | Best Suited To |
||||||
| Offline Installer (this guide) | Single-host production or evaluation deployments | Pinned to the release you download | Partial (via harbor.yml) | VMs, bare-metal, air-gapped environments |
| Online Installer | Fast setup with a live internet connection | Pulls latest matching images at install time | Partial | Standard internet-connected hosts |
| Harbor Helm Chart | Clusters already running Kubernetes | Latest, via chart versioning | Yes | Production Kubernetes environments |

This guide uses the offline installer, which bundles all required container images into a single archive — it’s the most predictable method and works identically whether or not the host has outbound internet access after the initial download.

Architecture: How Harbor’s Components Fit Together

Harbor is deployed as a set of Docker containers orchestrated by Docker Compose, sitting in front of the standard OCI Distribution registry.

                    ┌───────────────────────────────────────────────────┐
                    │                Ubuntu 26.04 LTS Host              │
                    │                                                   │
                    │   ┌───────────────┐        ┌────────────────────┐  │
                    │   │  Nginx Proxy  │◄───────┤  docker push/pull   │  │
                    │   │  (TLS :443)   │        │  (client)           │  │
                    │   └───────┬───────┘        └────────────────────┘  │
                    │           │                                        │
                    │  ┌────────┼─────────────────────────────────────┐  │
                    │  ▼        ▼             ▼             ▼         │  │
                    │ ┌────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │  │
                    │ │  Core  │ │ Registry │ │  Portal  │ │  Trivy │ │  │
                    │ │(API/   │ │(OCI      │ │  (Web UI)│ │(scanner) │  │
                    │ │ Auth)  │ │ storage) │ │          │ │        │ │  │
                    │ └───┬────┘ └────┬─────┘ └──────────┘ └────────┘ │  │
                    │     │           │                                 │
                    │     ▼           ▼                                 │
                    │ ┌────────┐ ┌────────────┐  ┌───────────────────┐  │
                    │ │Postgres│ │ Job Service│  │  Redis / Valkey    │  │
                    │ │ (DB)   │ │ (async jobs)│  │  (cache & queue)   │  │
                    │ └────────┘ └───────────┘  └───────────────────┘  │
                    │                                                   │
                    │           /data/harbor (persistent volume)        │
                    └───────────────────────────────────────────────────┘

The Nginx proxy container terminates TLS and routes traffic to Core (API and auth), the Registry service (actual blob/image storage), the Portal (Vue-based web UI), and Trivy for scanning — all backed by PostgreSQL for metadata and Redis (Valkey, as of Harbor 2.15.2) for caching and job queues.

Step 1: Install Docker and Docker Compose

sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] 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 update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify both are installed and meet the minimum versions:

docker version
docker compose version

Add your user to the docker group so you don’t need sudo for every Docker command (log out and back in afterward):

sudo usermod -aG docker $USER

Step 2: Download the Harbor Installer

cd /tmp
curl -L https://github.com/goharbor/harbor/releases/download/v2.15.2/harbor-offline-installer-v2.15.2.tgz \
  -o harbor-offline-installer-v2.15.2.tgz

Extract it to /opt:

sudo tar xzf harbor-offline-installer-v2.15.2.tgz -C /opt/
sudo chown -R "$(id -u):$(id -g)" /opt/harbor
cd /opt/harbor

Step 3: Generate TLS Certificates

Running Harbor without TLS causes Docker clients to reject the registry by default, so set up certificates before configuring harbor.yml. For internal/lab use, a self-signed certificate is sufficient:

sudo mkdir -p /etc/harbor/certs
cd /etc/harbor/certs

openssl req -x509 -nodes -sha512 -days 3650 \
  -newkey rsa:4096 \
  -keyout harbor.key \
  -out harbor.crt \
  -subj "/CN=registry.example.com/O=bckinfo/C=ID"

Replace registry.example.com with your actual hostname or IP. For a certificate trusted without client-side workarounds, request one from your organization’s internal CA or a public CA like Let’s Encrypt instead.

Step 4: Configure harbor.yml

Copy the template and edit it:

cd /opt/harbor
cp harbor.yml.tmpl harbor.yml
nano harbor.yml

Key settings to update:

hostname: registry.example.com

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

harbor_admin_password: ChangeThisStrongPassword123!

database:
  password: ChangeThisDbPassword123!

data_volume: /data/harbor

trivy:
  ignore_unfixed: false
  skip_update: false

Never leave harbor_admin_password or database.password at their template defaults — Harbor’s default admin credentials are widely known and actively targeted by internet-wide scanners against exposed registries.

Step 5: Run the Installer

Run the preparation and installation script, including the --with-trivy flag to enable vulnerability scanning:

sudo ./install.sh --with-trivy

The script validates your Docker/Compose versions, generates the internal Compose file from harbor.yml, pulls the bundled images, and starts all containers. This typically takes 2–5 minutes depending on disk speed.

Verifying the Installation

Confirm every Harbor container is Up (or healthy):

sudo docker compose -f /opt/harbor/docker-compose.yml ps

Expected containers include nginx, harbor-core, harbor-db, harbor-portal, registry, registryctl, harbor-jobservice, redis, and trivy-adapter (when --with-trivy was used).

Check that HTTPS responds correctly:

curl -k https://registry.example.com/api/v2.0/systeminfo

A JSON response containing Harbor’s version and configuration confirms the API is reachable.

Logging Into the Harbor UI

Open a browser and navigate to:

https://registry.example.com

Log in with:

  • Username: admin
  • Password: the value you set for harbor_admin_password in harbor.yml

Immediately change the admin password from User Profile → Change Password if you plan to keep this instance running beyond initial testing.

Creating a Project and Pushing Your First Image

Step 1 — Create a project in the UI

In the Harbor UI, go to Projects → New Project, name it (e.g., library), and choose Private or Public access.

Step 2 — Trust the certificate on the Docker client (self-signed only)

sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp /etc/harbor/certs/harbor.crt /etc/docker/certs.d/registry.example.com/ca.crt
sudo systemctl restart docker

Step 3 — Log in from the Docker CLI

docker login registry.example.com -u admin

Step 4 — Tag and push an image

docker pull nginx:stable-alpine
docker tag nginx:stable-alpine registry.example.com/library/nginx:stable-alpine
docker push registry.example.com/library/nginx:stable-alpine

Step 5 — Confirm it appears in the UI

Navigate to Projects → library → Repositories — the pushed image, its tags, size, and (if scanning is enabled) vulnerability summary should be listed.

Enabling Vulnerability Scanning with Trivy

If you installed with --with-trivy, scanning is available immediately:

  1. In the Harbor UI, go to your project’s Repositories tab
  2. Select an image tag and click Scan
  3. Once complete, the UI shows a severity breakdown (Critical/High/Medium/Low)

To scan automatically on every push, enable Automatically scan images on push under Project → Configuration. You can also enforce a Prevent vulnerable images from running policy that blocks pulls of images above a chosen severity threshold.

Managing Harbor as a systemd Service

Docker Compose doesn’t automatically restart Harbor’s containers on host reboot unless configured to. Create a systemd unit that wraps docker compose up/down:

sudo nano /etc/systemd/system/harbor.service
[Unit]
Description=Harbor Container Registry
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/opt/harbor
ExecStart=/usr/bin/docker compose -f /opt/harbor/docker-compose.yml up -d
ExecStop=/usr/bin/docker compose -f /opt/harbor/docker-compose.yml down

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now harbor
sudo systemctl status harbor

Troubleshooting

| Symptom | Likely Cause | Fix |
||||
| x509: certificate signed by unknown authority on docker push | Docker client doesn’t trust the self-signed certificate | Copy harbor.crt into /etc/docker/certs.d/<hostname>/ca.crt and restart Docker |
| install.sh fails with a Docker/Compose version error | Docker or Compose below the minimum required version | Upgrade to Docker 20.10.10+ and Compose 1.18.0+, then re-run ./install.sh |
| Harbor UI loads but login fails with correct password | Stale browser session or clock skew between client and server | Clear cookies for the Harbor domain, or verify NTP sync with timedatectl status |
| harbor-db container repeatedly restarting | database.password mismatch after editing harbor.yml post-install | Stop Harbor, restore the original password used at first install, or follow Harbor’s documented DB password-reset procedure |
| Push fails with blob upload unknown | Registry storage volume ran out of disk space | Check df -h on the data_volume path and expand storage |
| Trivy scans stuck in Pending | trivy-adapter container not running (installed without --with-trivy) | Re-run install.sh --with-trivy, or enable it separately per Harbor’s scanner configuration docs |
| Can’t reach Harbor after host reboot | Containers didn’t restart automatically | Set up the systemd unit shown above, or run docker compose -f /opt/harbor/docker-compose.yml up -d manually |
| 502 Bad Gateway from Nginx | harbor-core container still starting or crashed | Run sudo docker compose -f /opt/harbor/docker-compose.yml logs core to check for errors |

Uninstalling Harbor

cd /opt/harbor
sudo docker compose down -v
sudo systemctl disable --now harbor 2>/dev/null
sudo rm -rf /opt/harbor
sudo rm -rf /data/harbor
sudo rm /etc/systemd/system/harbor.service

This removes all Harbor containers, associated volumes, and stored images — back up /data/harbor first if you need to retain any pushed images.

FAQ

Does Harbor require Kubernetes to run?
No. The offline installer used in this guide runs entirely on Docker Compose on a single host. Kubernetes is only needed if you choose the Harbor Helm chart deployment method instead.

What’s the minimum Docker version Harbor supports?
Docker Engine 20.10.10-ce or newer, with Docker Compose 1.18.0 or newer (the docker compose plugin satisfies this requirement).

Can I use Harbor without TLS?
Technically yes, but Docker clients treat non-TLS registries as insecure by default and require an insecure-registries override in daemon.json on every client — TLS (even self-signed) is strongly recommended.

Does Harbor replace Docker Hub entirely?
For private, self-hosted image storage, yes. Harbor also supports replication rules to sync images to or from Docker Hub, another Harbor instance, or other OCI-compliant registries, so many teams use it alongside public registries rather than as a full replacement.

What changed in Harbor 2.15.x regarding caching?
As of Harbor 2.15.2, Redis was replaced with Valkey as the default cache and job queue backend — functionally compatible, but worth noting if you have monitoring or tooling that specifically expects a Redis process name.

(Visited 2 times, 3 visits today)

You may also like