Harbor Developer Guide
Quick reference for pushing and pulling container images against anton's Harbor registry.
TL;DR endpoints
| Use | Endpoint | Auth |
|---|---|---|
docker push (on-LAN laptop) | 192.168.1.106 | admin or robot account |
docker push (remote laptop) | registry.<tailnet-name>.ts.net | admin or robot account, via Tailscale |
docker pull from cluster Pod | 192.168.1.106/library/<image> | none — library is anonymous-pull |
| Web UI | https://registry.<tailnet-name>.ts.net | admin or your account |
Platform note. Cluster nodes run linux/amd64. On an Apple Silicon Mac,
always build with --platform linux/amd64 or Pods will fail to start.
Getting credentials
# Admin password (for the Harbor web UI and for pushing)
kubectl -n registries get secret harbor-admin-secret \
-o jsonpath='{.data.HARBOR_ADMIN_PASSWORD}' | base64 -d
You'll typically create a scoped robot account in the Harbor UI for scripted/CI pushes rather than reusing the admin credentials directly.
Docker insecure-registry setup (LAN)
Harbor's LAN endpoint is HTTP. Docker requires explicit opt-in.
Docker Desktop → Settings → Docker Engine → add:
{
"insecure-registries": ["192.168.1.106"]
}
Apply & restart the engine.
Remote/Tailscale path is HTTPS via the operator's issued cert; no
insecure-registry flag is needed there, but docker login targets the
Tailscale hostname.
Push an image
# On-LAN, HTTP (requires insecure-registries config above):
docker login 192.168.1.106
docker tag myapp:v1 192.168.1.106/library/myapp:v1
docker push 192.168.1.106/library/myapp:v1
Both resolve to the same Harbor and the same SeaweedFS harbor bucket.
library is the default public project; ask for a new project in the
Harbor UI if you want auth-gated pulls or per-team separation.
Direct push to registry.<tailnet-name>.ts.net is currently broken.
Harbor's auth realm returns an http:// URL on the tailnet hostname,
but the Tailscale operator ingress only serves 443. Use port-forward
as a workaround:
kubectl -n registries port-forward svc/harbor 8080:80 &
docker login localhost:8080 -u admin
docker tag myapp:v1 localhost:8080/library/myapp:v1
docker push localhost:8080/library/myapp:v1
Details + fix options: harbor-registry.md troubleshooting section.
Pull from Kubernetes (no pull secret needed)
library is configured for anonymous pull, so cluster Pods can reference
Harbor images directly:
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: default
spec:
containers:
- name: myapp
image: 192.168.1.106/library/myapp:v1
No imagePullSecrets, no ServiceAccount patching, no per-namespace
docker-registry Secret. The Talos machine-registries patch plus the
Spegel peer cache handles discovery; anonymous-pull handles auth.
For private projects, create a robot account in the Harbor UI with
Pull scope on that project, stash its token in 1Password, and pull it
into the target namespace via an ExternalSecret of kind
kubernetes.io/dockerconfigjson.
Build + push in one shot (ARM Mac)
docker build --platform linux/amd64 -t 192.168.1.106/library/myapp:v1 .
docker push 192.168.1.106/library/myapp:v1
Verify the manifest architecture:
docker manifest inspect 192.168.1.106/library/myapp:v1 \
| jq '.manifests // [{architecture}] | .[].platform // .[].architecture'
Off-LAN push — use kubectl port-forward until the auth-realm issue is fixed
Off-LAN with a working Tailscale session, the port-forward pattern also
works since kubectl routes through Tailscale:
kubectl -n registries port-forward svc/harbor 8080:80 &
docker login localhost:8080 -u admin
docker push localhost:8080/library/myapp:v1
The Tailscale hostname direct-push (registry.<tailnet-name>.ts.net) is
currently blocked by a Harbor realm-URL issue documented in
harbor-registry.md. Pulls (anonymous, from in-cluster Pods) are
unaffected.
Troubleshooting
unauthorized on push/pull
Robot tokens expire or admin passwords drift. docker login again.
Image pull failed from a Pod
Two likely causes:
- Talos machine-registries patch not applied on that node. Verify:
Should show
talosctl --endpoints <tailscale-ip> --nodes <tailscale-ip> \
get machineconfig -o yaml | yq '.spec.machine.registries.mirrors'192.168.1.106 → http://192.168.1.106. - Non-
libraryproject without auth. Create a robot account for the project and project-scopedimagePullSecret.
Architecture mismatch
Check docker inspect myapp:v1 | jq '.[0].Architecture'. If it says arm64,
rebuild with --platform linux/amd64.
Can reach curl http://192.168.1.106 but push fails with TLS
You didn't add 192.168.1.106 to Docker's insecure-registries.
Further reading
- Harbor registry architecture — endpoints, storage backend, admin credentials, troubleshooting the cluster side.
- ADR 0015 — the decision record for running Harbor on SeaweedFS S3 with
anonymous pull on
library.