Deploying a Secure Private Container Registry on a Single Linux VPS
Step-by-step guide to hosting a private OCI-compliant container registry using Podman or Docker with TLS termination, htpasswd authentication, and automated garbage collection.
Netbay Developer Relations
Netbay Engineering
On this page
Public container registries such as Docker Hub or GitHub Packages impose rate limits, egress bandwidth costs, and third-party dependency risks for proprietary production services. Hosting your own OCI-compliant container registry on an unmanaged Linux VPS delivers private, high-speed image distribution, instant layer transfers across internal networks, and total control over image lifecycle retention.
The official CNCF Distribution registry (v2) is lightweight, written in Go, and consumes under 60MB of RAM, making it ideal for hosting on an economical VPS instance.
When you push a container image, the OCI client splits the image into two primary components: content-addressable blobs (the tarballs representing each filesystem layer, indexed by their SHA256 cryptographic digest) and manifests (JSON documents describing the layers, environment variables, entrypoints, and architecture). A private registry stores these blobs on disk and handles HTTP Range requests so clients can resume interrupted uploads or download layers concurrently in parallel streams.
Setting Up Directory Structures and Authentication Credentials
Start by generating bcrypt-hashed credentials using the apache2-utils htpasswd tool and creating persistent storage directories for container image layers:
# Install htpasswd generation utility
sudo apt-get update && sudo apt-get install -y apache2-utils
# Create dedicated directory hierarchy
sudo mkdir -p /opt/registry/{data,auth,certs}
# Generate bcrypt credentials for registry administrator
sudo htpasswd -B -c /opt/registry/auth/htpasswd ci-deployerUsing bcrypt (-B) ensures resistance against dictionary and rainbow table attacks. Always avoid legacy MD5 or crypt hash algorithms for registry authentication.
Configuring the Distribution Registry Service
Deploy the registry engine using Docker Compose or Podman. The following configuration binds the registry to localhost:5000, delegating external TLS termination and HTTP header handling to your reverse proxy:
version: "3.8"
services:
registry:
image: registry:2.8.3
restart: always
ports:
- "127.0.0.1:5000:5000"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true"
REGISTRY_HTTP_ADDR: "0.0.0.0:5000"
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/auth:/auth:roThe environment variable REGISTRY_STORAGE_DELETE_ENABLED: "true" is critical: by default, the Distribution registry prohibits image deletion to protect against accidental data loss. Enabling deletion allows CI/CD systems to clean up stale ephemeral feature-branch builds via the Registry V2 HTTP API.
Start the service with docker compose -f /opt/registry/docker-compose.yml up -d.
Client Authentication and Image Pushes
Configure your development workstation or CI runner to authenticate against the new private registry and push container images:
# Authenticate against your registry domain
podman login registry.example.com -u ci-deployer
# Tag an existing image for your private endpoint
docker tag myorg/api:v1 registry.example.com/production/api:v1
# Push the image layers to the private registry
docker push registry.example.com/production/api:v1
# Verify catalog entries via authenticated REST API
curl -u ci-deployer https://registry.example.com/v2/_catalogBecause the registry is hosted within your private network or on high-speed datacenter infrastructure, pulling a 500MB container image across instances takes seconds rather than minutes, eliminating deployment bottlenecks during large releases.
Automating Garbage Collection to Reclaim Disk Space
When an image tag is overwritten or deleted via the API, the associated filesystem blob layers remain on disk until garbage collection runs. The registry marks blobs as untagged but does not immediately remove them to avoid race conditions with concurrent uploads. Execute garbage collection inside the container:
# Execute dry-run garbage collection to see reclaimable bytes
docker exec -it opt_registry_1 registry garbage-collect --dry-run /etc/docker/registry/config.yml
# Execute live garbage collection to purge unreferenced blobs
docker exec -it opt_registry_1 registry garbage-collect /etc/docker/registry/config.ymlSchedule a weekly systemd timer to automate garbage collection during low-traffic maintenance windows.
Hosting a self-contained container registry provides lightning-fast image pull speeds and absolute infrastructure independence.
You can spin up an Ubuntu 24.04 instance on Netbay in under 60 seconds with Intel Xeon Platinum compute and high-speed SSD storage in Lucknow — netbayhosts.in.
Keep reading
Containers & Orchestration
Container Network Isolation: Bridging Namespaces and Custom IPAM on Linux
2026-09-23
Containers & Orchestration
Lightweight Workload Orchestration with HashiCorp Nomad on a Linux VPS
2026-09-20
Containers & Orchestration
Hardening Docker Compose for Production: Secrets, Healthchecks, and Graceful Restarts
2026-09-14
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance