Docker Registry (Distribution) 3.1 on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Docker Registry (Distribution) 3.1 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Docker Registry is powered by the CNCF Distribution project — the reference open source container image registry that Docker Hub and most private registries are built on. It is a stateless, highly scalable server side application that stores and lets you distribute OCI and Docker container images.
The image installs the official pinned Distribution v3.1.1 release binary (a single static Go binary, sha256 verified) to /opt/registry/registry and runs it directly under systemd. Docker is not required to run the registry — the registry is a self contained server. The registry binary binds to loopback only (127.0.0.1:5000) and runs with no registry level authentication; nginx is the sole network facing surface and terminates TLS on port 443 while enforcing HTTP Basic authentication against a bcrypt htpasswd file.
Security by design — no anonymous access. A registry with no authentication is an open push and pull surface for anyone who can reach it. On the very first boot, this image generates a per instance admin credential (a random openssl rand -base64 24 secret) and writes it into the nginx htpasswd file, and it regenerates a fresh self signed TLS certificate for this specific VM (the certificate Subject Alternative Names include the VM public IP and hostname). Anonymous requests to the registry API return HTTP 401 with a WWW-Authenticate: Basic challenge; only the per instance credential is accepted. The credential is written to /root/registry-credentials.txt (mode 0600, root only).
What is included:
-
Docker Registry powered by CNCF Distribution v3.1.1 (official release binary, sha256 pinned) at
/opt/registry/registry, run under systemd as the unprivilegedregistrysystem user -
The registry API bound to loopback only (
127.0.0.1:5000) — never directly network exposed -
nginx terminating TLS on :443 and enforcing htpasswd Basic auth on the registry API, with
client_max_body_size 0andchunked_transfer_encoding onfor large layer pushes -
Port
:80returns a301redirect to HTTPS for every path except an unauthenticated/healthzendpoint (HTTP 200) for load balancer probes -
A per instance admin credential generated on first boot and stored in
/root/registry-credentials.txt(0600) — no anonymous access, no shared default password -
A self signed TLS certificate regenerated per VM on first boot (SAN includes the VM public IP + hostname) — replace it with your own CA signed certificate for production
-
Image blobs stored on a dedicated 20 GiB Azure data disk mounted at
/var/lib/registry— your pushed images survive independently of the OS disk and the volume is independently resizable -
Ubuntu 24.04 LTS base with latest security patches applied at build time
-
Azure Linux Agent for seamless cloud integration and SSH key injection
-
24/7 cloudimg support with guaranteed 24 hour response SLA
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the Docker Registry (Distribution) listing on Azure Marketplace
-
A machine with the Docker CLI installed to push and pull images (any recent Docker Engine or Docker Desktop)
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for a team registry and light workloads. Registries serving many concurrent pulls or storing large images should use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger, and a larger data disk.
Step 1: Deploy from the Azure Portal
Search Docker Registry in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin), TCP 443 (registry API over HTTPS), and TCP 80 (HTTP to HTTPS redirect and health probe) from your client networks.
Step 2: Deploy from the Azure CLI
RG="registry-prod"; LOCATION="eastus"; VM_NAME="registry-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/docker-registry-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name registry-vnet --address-prefix 10.100.0.0/16 --subnet-name registry-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name registry-nsg
az network nsg rule create -g "$RG" --nsg-name registry-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name registry-nsg --name allow-https --priority 110 \
--destination-port-ranges 443 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name registry-nsg --name allow-http --priority 120 \
--destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name registry-vnet --subnet registry-subnet --nsg registry-nsg --public-ip-sku Standard
Step 3: Connect via SSH and retrieve your registry credential
The per instance admin credential is generated on the first boot and stored in a root only file. Connect over SSH and read it:
ssh azureuser@<public-ip>
sudo cat /root/registry-credentials.txt
The file contains the registry URL, the username (admin) and the per instance password, plus quick start commands. Store the password somewhere safe — it is unique to this VM.
Step 4: Verify the registry and nginx services
Confirm both services are active and that the registry is bound to loopback only. Note in the listener output that 127.0.0.1:5000 (the registry) is loopback only, while nginx listens publicly on :443 and :80:
systemctl status registry nginx --no-pager
ss -tln | grep -E ':5000|:443|:80 '

Step 5: Check the health endpoint
The /healthz endpoint is served by nginx without authentication (ideal for load balancer and Azure health probes) and returns HTTP 200:
curl -k -o /dev/null -w 'healthz: HTTP %{http_code}\n' https://127.0.0.1/healthz
Step 6: Query the registry API and confirm anonymous access is rejected
Use the per instance credential to list repositories through the registry API, then confirm that an anonymous request (no credential) is rejected with HTTP 401. The self signed certificate lives at /etc/nginx/tls/registry.crt, so we pass it with --cacert:
PASS=$(sudo grep '^registry.admin.pass=' /root/registry-credentials.txt | cut -d= -f2-)
echo "# Authenticated catalog request:"
sudo curl -s -u "admin:$PASS" --cacert /etc/nginx/tls/registry.crt https://127.0.0.1/v2/_catalog; echo
echo "# Anonymous request (no credential) must be rejected:"
curl -s -o /dev/null -w 'anonymous /v2/ -> HTTP %{http_code}\n' --cacert /etc/nginx/tls/registry.crt https://127.0.0.1/v2/
The authenticated request returns the repository list as JSON; the anonymous request returns HTTP 401. There is no anonymous access to this registry.

Step 7: Push and pull images with the Docker CLI
From any machine with Docker installed, first trust the registry's self signed certificate (or replace it with your own CA signed certificate — see Step 9), then log in with your per instance credential and push an image. Replace <public-ip> with your VM's public IP or DNS name.
Copy the certificate from the VM and install it as a trusted CA for this registry:
scp azureuser@<public-ip>:/etc/nginx/tls/registry.crt registry.crt
sudo mkdir -p /etc/docker/certs.d/<public-ip>
sudo cp registry.crt /etc/docker/certs.d/<public-ip>/ca.crt
Log in, tag an image for your registry, push it, then pull it back:
docker login <public-ip> -u admin
docker pull alpine:latest
docker tag alpine:latest <public-ip>/team/alpine:1.0
docker push <public-ip>/team/alpine:1.0
docker pull <public-ip>/team/alpine:1.0
The screenshot below shows a complete round trip against this registry: docker login succeeds with the per instance credential, docker push uploads the image layers, and after removing the local copy, docker pull retrieves the identical image back from the registry (matching digest).

Step 8: How the registry is wired
The registry configuration at /etc/docker/registry/config.yml uses the filesystem storage driver with its root directory on the dedicated data disk (/var/lib/registry) and binds the HTTP API to loopback only. nginx is the only network facing surface: it terminates TLS, enforces htpasswd Basic auth on /v2/, and proxies to the registry with the headers container clients require (Host, X-Forwarded-*, unlimited body size and chunked transfer for large layers, and the Docker-Distribution-Api-Version header).
grep -E 'rootdirectory|addr:|enabled: true|storagedriver' /etc/docker/registry/config.yml
grep -nE 'listen 443 ssl|ssl_certificate|client_max_body_size|chunked_transfer|auth_basic|Docker-Distribution|proxy_pass|return 301' /etc/nginx/sites-available/cloudimg-registry

Step 9: Use your own domain and a trusted certificate (production)
The self signed certificate is a convenience for getting started. For production, front the registry with your own DNS name and a CA signed certificate so clients trust it without any certs.d step:
-
Point a DNS record (for example
registry.example.com) at the VM public IP. -
Replace
/etc/nginx/tls/registry.crtand/etc/nginx/tls/registry.keywith your CA signed certificate and key (for example from Let's Encrypt or your internal CA), then runsudo nginx -t && sudo systemctl reload nginx. -
Clients then simply
docker login registry.example.comwith no certificate trust step.
Step 10: Scale the storage backend
For larger deployments, Distribution supports object storage backends. To store blobs in Azure Blob Storage or S3 instead of the local data disk, edit the storage section of /etc/docker/registry/config.yml (replace the filesystem driver with azure or s3 and its credentials), then sudo systemctl restart registry. See the Distribution documentation for the full set of storage driver options.
Step 11: Server components
| Component | Version / Detail |
|---|---|
| Registry | CNCF Distribution v3.1.1 (official release binary, sha256 pinned) |
| Registry binding | 127.0.0.1:5000 (loopback only, no auth at this layer) |
| Front proxy | nginx (TLS on :443, htpasswd Basic auth, :80 -> 301 https) |
| Storage | filesystem driver, root /var/lib/registry on a dedicated 20 GiB data disk |
| Credential | per instance admin password, generated first boot, /root/registry-credentials.txt (0600) |
| TLS certificate | self signed, regenerated per VM first boot (SAN = public IP + hostname), /etc/nginx/tls/ |
| Operating system | Ubuntu 24.04 LTS (patched at build) |
| License | Apache-2.0 (Distribution) |
Step 12: Managing the registry service
sudo systemctl status registry
sudo systemctl restart registry
sudo journalctl -u registry -f
sudo systemctl reload nginx
Image blobs live under /var/lib/registry on the data disk and persist across service restarts and VM reboots.
Step 13: Security recommendations
-
Restrict the NSG. Allow TCP 443 (and 22 for admin) only from the client networks that need to push and pull. Do not expose the registry to the public internet unless you intend to.
-
Rotate the credential. To change the admin password, regenerate the htpasswd entry:
sudo htpasswd -B /etc/nginx/.htpasswd adminthensudo systemctl reload nginx. Update/root/registry-credentials.txtaccordingly. -
Add more users. Grant additional accounts with
sudo htpasswd -B /etc/nginx/.htpasswd <username>and reload nginx. -
Use a trusted certificate (Step 9) for production so clients validate the registry without a manual trust step.
-
Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
-
Back up
/var/lib/registry(the data disk) to preserve your pushed images.
Step 14: Support and Licensing
Docker Registry is built on the CNCF Distribution project, distributed under the Apache License 2.0. This cloudimg image bundles the unmodified official release binary. cloudimg provides the packaging, hardening, per instance credential and TLS automation, and 24/7 support with a guaranteed 24 hour response SLA.
Deploy on Azure
Find Docker Registry (Distribution) 3.1 on Ubuntu 24.04 on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.