
A Harbor of our own for the cluster's images and charts
Running your own image registry sounds like something you only need when there is a compliance requirement behind it. I set one up for a simpler reason: I did not want deploying to production to depend on someone else's service being available and in a good mood, or on the public registry's pull rate limit.
Images and charts in the same place
Harbor stores container images and also Helm charts as OCI artifacts. In the homelab, the entire application is packaged as a chart stored there, and that chart defines the resources for the website, the MCP server, the agent runtime and the model gateway.
helm push zetesis-portal-${VERSION}.tgz oci://gauss.nexolabs.dev/zetesis-portalHaving everything in the same registry removes one question at deploy time: the image version and the version of the chart that deploys it live together, with the same credentials and the same access policy. One project per system, private by default.
Who writes there
Nobody with a first and last name. Deployments and nodes do not use my user account: Harbor has robot accounts — service credentials with permissions scoped to a single project and no ability to administer anything.
The split is the usual one and it is worth saying out loud: continuous integration is the only thing that writes, and the cluster only reads. A leaked cluster-side credential cannot be used to publish a poisoned image, because that credential does not know how to write.
Versions pile up
Each artifact keeps its digest, its size and its version tag. This is where the registry stops being abstract: you can see a service's entire history, version by version, with when each one was pushed and when it was last pulled.
That last column is more useful than it looks. An image nobody has pulled in months is a candidate for deletion, and without that data, cleanup is guesswork.
Where integration ends and deployment begins
The registry is not some storage on the side: it is the border between the two halves of the system. Continuous integration builds and writes there; from that point on, nobody builds anything again — you only pull what already exists.
ArgoCD is the reader. In production it pulls the chart from the registry itself as an OCI artifact, and the images referenced by that chart come from the same place:
- repoURL: oci://gauss.nexolabs.dev/zetesis-portal/zetesis-portal
chart: zetesis-portalDeploying a new version means changing a tag in a configuration file in the infrastructure repository. ArgoCD sees the change, pulls that version from the registry and reconciles. Nobody runs a deployment by hand, and rolling back is reverting a commit: the previous version is still in the registry, untouched.
The preview environments work the same way, with one deliberate difference: the chart does not come from the registry but from the pull request's commit, because in a preview you want to test the chart exactly as you left it on the branch, not the latest published one. The images do come from here, tagged with the PR number and the commit.
That pace is also the argument for not depending on someone else's registry. With several pull requests open, an hourly pull limit stops being a theoretical number and becomes the reason an environment fails to come up.
Where it runs, and why not with the application
I run two clusters: one for the platform —the secrets manager, the registry and the observability stack— and another for the public application. The registry lives in the platform one, on purpose.
If it ran in the same cluster as the application, an incident that takes that cluster down leaves you without the application and without the means to redeploy it. Losing the registry is not losing what is already running: it is losing the ability to start it again.
What I don't have, visible in the screenshots
Harbor ships with vulnerability scanning, artifact signing and SBOM generation. In my projects all three columns say the same thing: not supported, unsigned. It is not that Harbor cannot do it; it is that I have not configured it.
For a homelab where I am the only one publishing, the risk that signing covers is low. The moment someone else publishes, that stops being true: signing is what lets you verify that the image you are deploying was built by your continuous integration and not by something else with the same name.
Writing it down here is the cheap way to make it stop being invisible. The missing rule is not complicated —keep the last N versions and whatever is deployed, delete the rest— and Harbor enforces it on its own from its own configuration.
When not to set one up
With one deployment a month and a single consumer, it is not worth it: it is one more service with its storage, its TLS and its credentials, in exchange for solving a problem you do not have yet.
The tipping point comes when deploying often starts hitting the public registry’s limits, or when the first image appears that cannot leave the house. From there on, the cost of operating it is lower than the cost of living without it.
Keep reading

Building a homelab from scratch: Kubernetes and GitOps - Homelab (03/06)

A full environment for every pull request — deleted automatically

Setting up a homelab from scratch: backups and restore - Homelab (06/06)

One deployment per customer or one space per customer

Search and agents over your own documentation
