March 28, 2025 | 00:19 | 4 minutes

Setting up Harbor as private Docker registry

Because I didn’t want to push all my custom images and sourcecode to public repos like hub.docker.com, I was looking for a self-hosted solution. After checking out the features of the official Docker registry, I was a bit underwhelmed by the missing repo based auth and the lack of combining the use as repository and cache.

Why would you need private Docker registry?

First if you don’t feel comfortable in sharing all your images with a third party you’ll need to find a more local way of distributing your images. Also Docker changed their policies and limit the maximum pulls you can do to 10 or 100 pulls per hour (depending if your autheticated or not), and while this may seem like a big number, I can assure you it’s easy to reach it. Imagine you got a kubernetes cluster, and you restart some nodes because of an update. Now every node is pulling every image it needs if you’ve set your imagePullPolicy to Always. I really like to keep my deployments configured that way, because it makes updates via kubectl rollout restart deployment x really easy for uncritical services.

To mitigate the pull limits, we will use a cache in our private repository.

Candidates

I spent quite some time on digging through the different options. While doing so, I checked out the following projects:

Portus

After looking into Portus, I quickly realised that this project is quite dead. The last commit dated back to Mar 25, 2020 and was only a change in the CHANGELOG.md, also the last release is from Mar 19, 2019.

JFrog Container Registry

I know JFrog from work and know it works, but it’s closed source, and I think it’s quite overkill for my private use-cases.

Harbor

Checking out Harbor, I quickly noticed the CNCF banner on their website. The graduated state in CNCF means it’s stable and should be quite easy to handle.

Also, they have an official helm3 chart available.

Deploying

To get the helm chart we will use the bitnami chart via oci.

# get default values
helm show values oci://registry-1.docker.io/bitnamicharts/harbor > values.yaml

After fetching the values we need to change some stuff to match our infrastructure. For example I needed to set the ingress to LoadBalancer and change the StorageClass.

You should walk through the default values and adjust everything to your needs, here I’ll share my values file without any secrets (you’ll need to change all those xxx)

registry:
  secret: xxx
  credentials:
    password: xxx
jobservice:
  secret: xxx
core:
  secret: xxx
  xsrfKey: xxx
expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: "hub.marschall.systems-tls"
  ingress:
    hosts:
      core: "hub.marschall.systems"
    controller: default
    className: "nginx-dmz"
    annotations:
      ingress.kubernetes.io/ssl-redirect: "true"
      ingress.kubernetes.io/proxy-body-size: "0"
      nginx.ingress.kubernetes.io/ssl-redirect: "true"
      nginx.ingress.kubernetes.io/proxy-body-size: "0"
      cert-manager.io/cluster-issuer: cloudflare-issuer
externalURL: https://hub.marschall.systems
ipFamily:
  ipv6:
    enabled: false
  ipv4:
    enabled: true
persistence:
  enabled: true
  resourcePolicy: "keep"
  persistentVolumeClaim:
    registry:
      storageClass: "vsan-default"
      accessMode: ReadWriteOnce
      size: 200Gi
    jobservice:
      jobLog:
      storageClass: "vsan-default"
        accessMode: ReadWriteOnce
        size: 1Gi
    redis:
      storageClass: "vsan-default"
      accessMode: ReadWriteOnce
      size: 1Gi
    trivy:
      storageClass: "vsan-default"
      accessMode: ReadWriteOnce
      size: 5Gi
  imageChartStorage:
    disableredirect: true
    type: filesystem
    filesystem:
      rootdirectory: /storage
imagePullPolicy: IfNotPresent
updateStrategy:
  type: Recreate
logLevel: info
existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD
harborAdminPassword: "xxx"
secretKey: "xxx"
database:
  type: external
  external:
    host: "db1.marschall.management"
    port: "5432"
    username: "xxx"
    password: "xxx"
    coreDatabase: "harbor"
    sslmode: "require"
  maxIdleConns: 10
  maxOpenConns: 50
metrics:
  enabled: true
  serviceMonitor:
    enabled: true
trace:
  enabled: false

Now we can install the application with

helm install --namespace harbor --name harbor --create-namespace oci://registry-1.docker.io/bitnamicharts/harbor -f values.yaml

Configuring pull through cache for DockerHub

To make use of the millions of public available images on DockerHub without running into the pull limits we no create a new upstream Registry in Harbor.

Step 1
Step 2

Now we need to map the Registry to a Project.

Step 3
Step 4

Congratulations you now have your own docker registry that can be used as pull through proxy! To do so just substitute docker.io with <yourregistry>/cache in my case docker.io/hugomods/hugo:git becomes hub.marschall.systems/cache/hugomods/hugo:git

© marschall.systems 2025

This Site is affected by your System Dark/Lightmode

Powered by Hugo & Kiss'Em.