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 opensslorcertbotavailable
1. Generate SSL Certificates
Option A – Self-signed certificate (for internal networks)
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)
sudo certbot certonly --standalone -d harbor.mydomain.com
2. Configure Harbor with HTTPS
Edit harbor.yml:
hostname: harbor.mydomain.com
https:
port: 443
certificate: /data/cert/harbor.crt
private_key: /data/cert/harbor.key
Re-deploy Harbor:
./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:
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:
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:
sudo systemctl restart containerd
4. Authenticate Harbor from K3s
Log in manually (for testing):
sudo ctr images pull harbor.mydomain.com/library/nginx:latest \
--user admin:Harbor12345
Or create a Kubernetes secret for use in Pods:
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:
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: trueinharbor.yml. - Use project-level RBAC for controlled image access.
- Regularly rotate certificates and update secrets in the cluster.
6. Troubleshooting
| Issue | Fix |
|---|---|
x509: certificate signed by unknown authority | Ensure the CA file (ca.crt) is trusted on all nodes. |
unauthorized: authentication required | Check Harbor credentials or project permissions. |
| Images not pulling | Verify 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.