Skip to content

On-Premise Container Registry: Secure Harbor Setup with K3s + Containerd

This guide walks through setting up a secure, self-hosted Harbor container registry and integrating it with K3s (containerd). This version replaces the insecure configuration from the previous guide and enables HTTPS and authentication for production-grade security.


Prerequisites

  • Harbor installed (via Docker Compose or Helm)
  • K3s cluster using containerd (default)
  • Host with domain or resolvable hostname (e.g., harbor.mydomain.com)
  • Root access or sudo
  • openssl or certbot available

1. Generate SSL Certificates

Option A – Self-signed certificate (for internal networks)

bash
mkdir -p /data/cert
openssl req -newkey rsa:4096 -nodes -sha256 -x509 -days 365 \
  -keyout /data/cert/harbor.key \
  -out /data/cert/harbor.crt \
  -subj "/C=PH/ST=Cavite/L=Imus/O=WayneEnterprise/OU=Dev/CN=harbor.mydomain.com"

Option B – Let’s Encrypt certificate (for public or domain-based access)

bash
sudo certbot certonly --standalone -d harbor.mydomain.com

2. Configure Harbor with HTTPS

Edit harbor.yml:

yaml
hostname: harbor.mydomain.com
https:
  port: 443
  certificate: /data/cert/harbor.crt
  private_key: /data/cert/harbor.key

Re-deploy Harbor:

bash
./prepare
docker-compose down -v
docker-compose up -d

Test access via browser:

https://harbor.mydomain.com

3. Trust Harbor Certificate in K3s Nodes

Copy the Harbor certificate to each node:

bash
sudo mkdir -p /etc/containerd/certs.d/harbor.mydomain.com/
sudo cp /data/cert/harbor.crt /etc/containerd/certs.d/harbor.mydomain.com/ca.crt

Create or edit /etc/containerd/certs.d/harbor.mydomain.com/hosts.toml:

toml
server = "https://harbor.mydomain.com"

[host."https://harbor.mydomain.com"]
  ca = "/etc/containerd/certs.d/harbor.mydomain.com/ca.crt"
  skip_verify = false

Restart containerd:

bash
sudo systemctl restart containerd

4. Authenticate Harbor from K3s

Log in manually (for testing):

bash
sudo ctr images pull harbor.mydomain.com/library/nginx:latest \
  --user admin:Harbor12345

Or create a Kubernetes secret for use in Pods:

bash
kubectl create secret docker-registry harbor-creds \
  --docker-server=https://harbor.mydomain.com \
  --docker-username=admin \
  --docker-password='Harbor12345' \
  --docker-email=admin@local

Use it in a Deployment manifest:

yaml
imagePullSecrets:
  - name: harbor-creds

5. Optional Hardening Steps

  • Enable Notary and Trivy for image signing and vulnerability scanning.
  • Switch to OIDC or LDAP for centralized authentication.
  • Enforce HTTPS redirect by setting relativeurls: true in harbor.yml.
  • Use project-level RBAC for controlled image access.
  • Regularly rotate certificates and update secrets in the cluster.

6. Troubleshooting

IssueFix
x509: certificate signed by unknown authorityEnsure the CA file (ca.crt) is trusted on all nodes.
unauthorized: authentication requiredCheck Harbor credentials or project permissions.
Images not pullingVerify hosts.toml paths and restart containerd.

Summary

You now have a secure Harbor registry connected to K3s via containerd with HTTPS, authentication, and proper trust configuration.

Harbor URL: https://harbor.mydomain.com
K3s Config Path: /etc/containerd/certs.d/harbor.mydomain.com/

This setup ensures secure image pulls across your cluster and sets the foundation for CI/CD integration and enterprise-grade image governance.