This guide builds on my earlier posts (read first):
- Install Sonatype Nexus 3 on Ubuntu 24.04 with HTTPS — https://www.maksonlee.com/install-sonatype-nexus-3-on-ubuntu-24-04-with-https/
- Host Your Own Docker Images on Sonatype Nexus 3 OSS (HTTPS via HAProxy, Ubuntu 24.04) — https://www.maksonlee.com/host-your-own-docker-images-on-sonatype-nexus-3-oss-https-via-haproxy-ubuntu-24-04/
We won’t repeat previous steps. This post only covers: creating the Docker Hub proxy repo, configuring HAProxy on :5002, testing, plus a short note on how Docker “mirrors” work.
What a Docker registry mirror is (quick)
- A mirror is a pull-through cache in front of an upstream registry (here: Docker Hub).
- First pull fetches from Hub and caches in Nexus; subsequent pulls come from Nexus at LAN speed.
- Docker’s daemon-wide "registry-mirrors"applies only to Docker Hub (images pulled without an explicit registry host, e.g.,alpine,ubuntu,nginx).
- For non-Hub registries (GHCR, Quay, ECR, GCR, etc.), you must prefix your Nexus host/port in the image reference.
- Create a Docker (proxy) repo to Docker Hub (Nexus UI)
Path: Settings → Repositories → Create repository → docker (proxy)
Change only these fields:
Repository Connectors
- HTTP → Port = 15002 (TLS terminates at HAProxy; leave HTTPS off)
Proxy
- Remote storage → https://registry-1.docker.io
(Recommended) HTTP → Authentication (to upstream)
- Username / Password → your Docker Hub account (reduces rate limiting)
Click Create repository (e.g., name it dockerhub-proxy).
Optional (public pulls): in this repo’s Settings, check Allow anonymous docker pull (Docker Bearer Token Realm must be active—already covered in your previous post).
- HAProxy: expose the proxy on :5002 (TLS) and forward to Nexus
Nexus listens on local 15002; HAProxy serves 5002.
/etc/haproxy/haproxy.cfg
# --- Docker proxy on :5002 (TLS at HAProxy, HTTP to Nexus) ---
frontend fe_nexus_docker_proxy_5002
        bind :5002 ssl crt /etc/haproxy/certs/nexus.maksonlee.com.pem alpn http/1.1
        mode http
        option forwardfor
        http-request set-header X-Forwarded-Proto https
        http-request set-header X-Forwarded-Port 5002
        default_backend be_nexus_docker_proxy_5002
backend be_nexus_docker_proxy_5002
        mode http
        balance leastconn
        option httpchk GET /v2/
        # 200 or 401 are both fine for unauthenticated Docker ping
        http-check expect status 200-499
        http-reuse always
        timeout queue   1m
        timeout server  10m
        server nexus_docker_proxy_15002 127.0.0.1:15002 check
Validate & reload:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
- Test
Reachability (200 or 401 both prove the path works)
curl -I https://nexus.maksonlee.com:5002/v2/
Pull (first time warms the cache)
docker pull nexus.maksonlee.com:5002/library/alpine:latest
- Set as a daemon-wide mirror — Docker Hub only
Create /etc/docker/daemon.json and restart:
sudo mkdir -p /etc/docker
printf '{\n  "registry-mirrors": ["https://nexus.maksonlee.com:5002"]\n}\n' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -A2 'Registry Mirrors'
Note:
- • For GHCR/Quay/ECR/GCR, "registry-mirrors"is ignored—use your Nexus proxy by prefixing the host in the image name (e.g.,docker pull nexus.maksonlee.com:5003/ORG/IMAGE:TAG).
- When using a daemon-wide mirror for Docker Hub, if the client has local Docker Hub credentials (docker login, entries forindex.docker.io/registry-1.docker.ioin~/.docker/config.json, or a credential helper), Docker may bypass the mirror and contact Hub directly. Remove or isolate Hub creds on machines that must use the mirror:docker logout, clean the Hub entries in~/.docker/config.json, or use separateDOCKER_CONFIGdirectories (e.g.,/etc/docker/config-mirrorvs./etc/docker/config-hub).
