Host Your Own Docker Images on Sonatype Nexus 3 OSS (HTTPS via HAProxy, Ubuntu 24.04)

Why use Nexus as a Docker Registry?

If you already run Sonatype Nexus and don’t want to stand up Harbor, Nexus can host/pull Docker images and act as a simple Docker proxy/mirror—while also managing Maven/npm/PyPI/etc. Choose Harbor when you need enterprise OCI features (built-in vulnerability scanning, project quotas/RBAC, replication). For a single-box artifact manager covering Docker + other package types, Nexus is lightweight and convenient.


TL;DR

  • Nexus UI: https://nexus.maksonlee.com → HAProxy:443 → 127.0.0.1:8081
  • Docker (hosted): https://nexus.maksonlee.com/v2/... → HAProxy:443 → 127.0.0.1:5001
  • Nexus: enable Docker Bearer Token Realm, create Docker (hosted) on HTTP 5001, optional anonymous pulls
  • HAProxy: split by path_beg /v2/

Usage:

docker login nexus.maksonlee.com
docker tag ubuntu:22.04 nexus.maksonlee.com/library/ubuntu:22.04
docker push nexus.maksonlee.com/library/ubuntu:22.04
docker pull nexus.maksonlee.com/library/ubuntu:22.04

Assumptions

  • Ubuntu 24.04, Nexus 3 OSS, HAProxy 3.2 with a valid cert for nexus.maksonlee.com
  • DNS nexus.maksonlee.com → your HAProxy host

Prerequisites

  • Nexus running at 127.0.0.1:8081 (UI)
  • HAProxy listening on :443 with a full-chain cert

  1. Enable Docker Realm

Settings → Security → Realms

  • Move Docker Bearer Token Realm to ActiveSave

  1. Create a Docker (hosted) Repository

Settings → Repository → Repositories → Create repository → docker (hosted)

  • Name: docker-hosted
  • HTTP:Port: 5001
  • HTTPS: leave unchecked (TLS handled by HAProxy)
  • Enable Docker V1 API: unchecked
  • Strict Content Type Validation: checked
  • Allow anonymous docker pull: optional
  • Blob store: default (or your custom)
  • Save

  1. HAProxy (UI on 8081, Docker on 5001 via /v2/)

Minimal patch from a single-backend setup:

# Redirect HTTP → HTTPS
frontend http_in
    bind *:80
    http-request redirect scheme https code 301 unless { ssl_fc }

# Terminate TLS and route by path
frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/nexus.maksonlee.com.pem alpn h2,http/1.1
    mode http
    # Docker Registry v2 API is always /v2/
    acl is_docker path_beg -i /v2/
    use_backend nexus_docker if is_docker
    default_backend nexus_ui

backend nexus_ui
    mode http
    option http-buffer-request
    option http-keep-alive
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    http-request set-header Host nexus.maksonlee.com
    server nexus1 127.0.0.1:8081 check

backend nexus_docker
    mode http
    option http-buffer-request
    option http-keep-alive
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    http-request set-header Host nexus.maksonlee.com
    server docker_hosted 127.0.0.1:5001 check

Reload:

sudo systemctl reload haproxy

Result

  • https://nexus.maksonlee.com/ → UI (127.0.0.1:8081)
  • https://nexus.maksonlee.com/v2/... → Docker hosted (127.0.0.1:5001)

  1. Namespaces (Conventions)

A single-segment name works with this setup (since only one Docker repo is behind /v2/):

docker tag ubuntu:22.04 nexus.maksonlee.com/ubuntu:22.04
docker push nexus.maksonlee.com/ubuntu:22.04

Still, prefer a namespace to stay future-proof (e.g., library/, makson/)—especially if later you enable Path based routing or add more repos behind the same port.


  1. Minimal RBAC (Privileges & Roles)

Pull-only

  • nx-repository-view-docker-docker-hosted-browse
  • nx-repository-view-docker-docker-hosted-read

Push (publish)

  • nx-repository-view-docker-docker-hosted-browse
  • nx-repository-view-docker-docker-hosted-read
  • nx-repository-view-docker-docker-hosted-add
  • nx-repository-view-docker-docker-hosted-edit

Allow delete (optional)

  • nx-repository-view-docker-docker-hosted-delete

All view-level perms (maintainers)

  • nx-repository-view-docker-docker-hosted-*

Avoid nx-repository-admin-* unless you want users to change repo settings.

Create roles

  • Settings → Security → Roles → Create role
  • docker-hosted-pullers → add browse + read
  • docker-hosted-publishers → add browse + read + add + edit (and optionally delete)
  • Settings → Security → Users → assign roles
  • (Optional) Anonymous pulls
    • Settings → Security → Anonymous Access: enable
    • Ensure anonymous effectively has browse + read on docker-hosted (via a read role)

  1. Test From a Client
# Login (TLS via HAProxy on 443)
docker login nexus.maksonlee.com

# Push a test image
docker pull ubuntu:22.04
docker tag ubuntu:22.04 nexus.maksonlee.com/library/ubuntu:22.04
docker push nexus.maksonlee.com/library/ubuntu:22.04

# Pull it back
docker pull nexus.maksonlee.com/library/ubuntu:22.04

Default client port: HTTPS → 443 (no need to specify :443).


Permission Sanity Check (Expected Failures)

Demonstrate auth vs anonymous behavior:

# 1) Push succeeds while logged in (publisher role)
docker login nexus.maksonlee.com
docker push nexus.maksonlee.com/library/ubuntu:22.04
# Expect: success with a digest line

# 2) Logout, push anonymously → should fail
docker logout nexus.maksonlee.com
docker push nexus.maksonlee.com/library/ubuntu:22.04
# Expect:
# ... Layer already exists
# unauthorized: access to the requested resource is not authorized

# 3) Anonymous pull may still work (if enabled)
docker pull nexus.maksonlee.com/library/ubuntu:22.04
# Expect: same digest as pushed

Why it fails: anonymous users never have push rights; push requires the repo view-level add/edit privileges.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top