Back to articles
Technology Insight

Building a Self-Hosted Private Docker Registry with a Web UI on a VPS: A Comprehensive Enterprise Guide

May 30, 2026

Introduction to Self-Hosted Container Registries

In modern cloud-native architecture, containerization has transitioned from a progressive trend to an absolute standard. As engineering teams scale, the volume of proprietary Docker images grows exponentially. While public or managed registries like Docker Hub, GitHub Container Registry (GHCR), or AWS ECR offer convenience, they often introduce escalating subscription costs, strict rate limits, and compliance challenges regarding data sovereignty. For enterprises seeking absolute control over their intellectual property, building a Private Docker Registry with a Web User Interface (UI) on a Virtual Private Server (VPS) is an exceptionally robust, cost-effective, and secure alternative.

This comprehensive architectural guide walks you through provisioning a self-hosted container registry from scratch. We will not only deploy the core storage registry but also integrate a visual management layer (Web UI), enforce strict HTTP Basic Authentication, and secure all communication channels using Let's Encrypt TLS/SSL certificates via an Nginx Reverse Proxy.

---

Prerequisites and Infrastructure Architecture

Before executing the deployment scripts, ensure your infrastructure meets the minimum baseline requirements. Operating a production-grade registry demands a stable environment to prevent pipeline disruptions during continuous integration and deployment (CI/CD) cycles.

System Requirements

  • VPS Specifications: A minimum of 2 vCPUs, 2GB RAM, and SSD storage scaled to your projected image volume. (Ubuntu 22.04 LTS or 24.04 LTS is highly recommended).
  • Network & Domain: A fully qualified domain name (FQDN) pointing to your VPS public IP via an A Record (e.g., registry.yourcompany.com).
  • Installed Software: Docker Engine (v20.10+) and Docker Compose (v2.0+) initialized on the host machine.

The system architecture will route external developer traffic through an encrypted Nginx gateway, which validates credentials before proxying requests to either the back-end storage registry or the front-end graphical user interface.

---

Step 1: Setting Up the Directory Structure and Environment

To maintain a clean, maintainable infrastructure-as-code footprint on your VPS, we will isolate all configuration profiles, persistent data volumes, and security certificates inside a unified directory hierarchy.

Execute the following commands in your terminal to initialize the workspace:

mkdir -p /opt/docker-registry/{data,auth,nginx,certs}
cd /opt/docker-registry

This structure isolates our persistent storage layer (data) from our authentication databases (auth) and server routing configurations (nginx), preventing catastrophic data loss during container upgrades.

---

Step 2: Configuring Secure Authentication Mechanism

A private registry must never be exposed openly to the public internet. To restrict pull and push permissions exclusively to authorized continuous integration nodes and developers, we implement standard htpasswd basic authentication using the MD5 or bcrypt encryption scheme.

Generate your encrypted credentials by running the following command, replacing admin_user and SecurePassword123 with your highly secure enterprise credentials:

sudo apt-get update && sudo apt-get install -y apache2-utils
htpasswd -B -c /opt/docker-registry/auth/registry.password admin_user
Security Best Practice: Ensure that the generated registry.password file possesses restrictive read/write permissions (e.g., chmod 600) to prevent unauthorized local users on the VPS from reading the credential hashes.
---

Step 3: Crafting the Orchestration Layer with Docker Compose

We will leverage Docker Compose to orchestrate three critical microservices simultaneously: the official upstream Docker Registry (V2), the Joxit Docker Registry UI (a sleek, responsive visual dashboard), and the Nginx Reverse Proxy.

Create a configuration file named docker-compose.yml within your root directory:

version: '3.8'

services:
  registry:
    image: registry:2
    container_name: docker-registry
    restart: always
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/auth
    networks:
      - registry-net

  registry-ui:
    image: joxit/docker-registry-ui:latest
    container_name: docker-registry-ui
    restart: always
    environment:
      - REGISTRY_TITLE=Enterprise Private Registry
      - REGISTRY_URL=http://registry:5000
      - SINGLE_REGISTRY=true
      - DELETE_IMAGES=true
    networks:
      - registry-net
    depends_on:
      - registry

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx:/etc/nginx/conf.d
      - ./certs:/etc/letsencrypt
    networks:
      - registry-net
    depends_on:
      - registry
      - registry-ui

networks:
  registry-net:
    driver: bridge

This compose layout establishes an isolated internal bridge network (registry-net). Crucially, the registry and registry-ui containers do not expose any ports directly to the host VPS network interfaces. They are accessible exclusively via the nginx gateway container, drastically minimizing the attack surface.

---

Step 4: Configuring Nginx Reverse Proxy and SSL Encryption

To safely transmit authentication headers and large image blobs, encrypting traffic via TLS is mandatory. Docker daemons strictly reject communication with insecure registries over standard HTTP by default.

Create a configuration script named registry.conf inside the /opt/docker-registry/nginx/ directory to orchestrate routing rules and payload thresholds:

server {
    listen 80;
    server_name registry.yourcompany.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name registry.yourcompany.com;

    ssl_certificate /etc/letsencrypt/live/[registry.yourcompany.com/fullchain.pem](https://registry.yourcompany.com/fullchain.pem);
    ssl_certificate_key /etc/letsencrypt/live/[registry.yourcompany.com/privkey.pem](https://registry.yourcompany.com/privkey.pem);
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Disable max upload limits to support massive image layers
    client_max_body_size 0;
    chunked_transfer_encoding on;

    # Core Registry Backend API
    location /v2/ {
        # Do not allow connections from old Docker clients lacking V2 support
        if ($http_user_agent ~ "^(docker/1\.(3|4|5(?![0-9]-g))|Go ).*$") {
            return 404;
        }
        
        proxy_pass http://registry:5000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 900;
    }

    # Graphical User Interface Web Frontend
    location / {
        proxy_pass http://registry-ui:80;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
Important Configuration Note: The rule client_max_body_size 0; is absolutely essential. By default, Nginx limits client upload request sizes to 1MB. Setting this parameter to 0 removes limits entirely, allowing multi-gigabyte production Docker images to be pushed without throwing 413 Request Entity Too Large errors.

To generate the necessary production SSL certificates, run standard Certbot automation on your host VPS to bind production keys directly into the mapped ./certs volume directory.

---

Step 5: Launching and Testing the Infrastructure Stack

With configurations solidified, initialize your ecosystem in detached execution mode:

docker compose up -d

Verify that all microservices are operational and healthy by inspecting the active process states:

docker compose ps

Interacting with Your Private Registry via CLI

To validate the system end-to-end, log in from a local machine or external development client using your custom domain and credentials:

docker login registry.yourcompany.com

Upon successful authentication, test the workflow by tagging and uploading an image:

docker pull alpine:latest
docker tag alpine:latest [registry.yourcompany.com/internal-alpine:1.0](https://registry.yourcompany.com/internal-alpine:1.0)
docker push [registry.yourcompany.com/internal-alpine:1.0](https://registry.yourcompany.com/internal-alpine:1.0)
---

Step 6: Navigating the Web UI

Once the image push confirms completion via your CLI terminal, open your preferred web browser and navigate directly to your domain: [https://registry.yourcompany.com](https://registry.yourcompany.com).

You will be greeted by a clean, highly intuitive, responsive visual dashboard displaying your newly uploaded repository catalog. The UI allows engineering leaders and DevOps professionals to easily:

  • Inspect distinct tag histories, build metadata, and image layer details.
  • Conveniently copy optimized pull configurations directly to clipboard structures.
  • Safely trigger garbage-collection routine targets to clean up old, unreferenced images and free up storage space on the underlying VPS volume.
---

Conclusion & Maintenance Recommendations

Congratulations! You have successfully built and deployed a production-ready, self-hosted Private Docker Registry with a clean Web UI on your own VPS. This deployment establishes independence from third-party hosting pricing fluctuations while significantly improving image download latency across internal cloud environments.

As you transition this configuration to production environments, remember to implement automated daily backups of the /opt/docker-registry/data and /opt/docker-registry/auth directories, setup cron jobs to regularly renew your Let's Encrypt certificates, and monitor your disk space continuously to ensure smooth, uninterrupted development workflows.

Building a Self-Hosted Private Docker Registry with a Web UI on a VPS: A Comprehensive Enterprise Guide | DPTCloud