Developer Tools Azure

Zot OCI Registry on Ubuntu 24.04 on Azure User Guide

| Product: Zot OCI Registry on Ubuntu 24.04 LTS on Azure

Overview

Zot is a production-ready, vendor-neutral OCI-native container image registry from the project-zot community. It stores OCI artifacts and container images and lets you push and pull with any standard client - docker, podman, skopeo or oras - while keeping a small, single-binary footprint. The cloudimg image installs Zot 2.1.17 (the full build, with the bundled web UI and search/CVE-scan extensions), runs it as a systemd service behind an nginx reverse proxy on port 80, stores the OCI blob storage, configuration and the htpasswd authentication database on a dedicated Azure data disk, and generates a unique admin account on the first boot of every VM. Backed by 24/7 cloudimg support.

What is included:

  • Zot 2.1.17 (Apache-2.0), the full build with the web UI and search/vulnerability extensions
  • The registry API and the web UI published on port 80 via nginx (with WebSocket upgrade and unlimited body size for large image-layer pushes)
  • htpasswd (bcrypt) authentication gating every endpoint - the OCI base /v2/ returns 401 until you authenticate
  • A dedicated Azure data disk at /var/lib/zot for OCI blob storage, config and the htpasswd auth database
  • A unique admin account generated on first boot, written to a root-only credentials file
  • zot.service + nginx.service as systemd units, enabled and active
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting point. NSG inbound: allow 22/tcp from your management network and 80/tcp for the registry and web UI (front with TLS for public exposure - see Enabling HTTPS).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Zot by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Review the dedicated data disk on the Disks tab, then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name zot \
  --image <marketplace-image-urn> \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open the registry/web-UI port on the VM's network security group:

az vm open-port --resource-group <your-rg> --name zot --port 80 --priority 1001

Step 3 - Retrieve the per-VM admin password

A unique admin account is generated on the first boot of every VM and written to a root-only credentials file. SSH in and read it:

ssh azureuser@<vm-public-ip>
sudo cat /root/zot-credentials.txt

The file contains the registry URL, the admin username (admin) and the generated password:

ZOT_URL=http://<vm-public-ip>/
ZOT_ADMIN_USER=admin
ZOT_ADMIN_PASSWORD=<ZOT_ADMIN_PASSWORD>

Keep this password safe - it is the credential for the web UI and for docker/podman login.

Step 4 - Confirm the registry is healthy

The OCI base endpoint /v2/ requires authentication, so it returns 401 without credentials and 200 once you authenticate as admin. Run these on the VM:

echo "Unauthenticated /v2/ (expect 401):"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/v2/
PASS=$(sudo grep '^ZOT_ADMIN_PASSWORD=' /root/zot-credentials.txt | cut -d= -f2-)
echo "Authenticated /v2/ (expect 200):"
curl -s -o /dev/null -w '%{http_code}\n' -u "admin:${PASS}" http://127.0.0.1/v2/
echo "nginx static health (expect ok):"
curl -s http://127.0.0.1/health

Expected output:

Unauthenticated /v2/ (expect 401):
401
Authenticated /v2/ (expect 200):
200
nginx static health (expect ok):
ok

Check the systemd services are active:

systemctl is-active zot.service nginx.service

Expected output:

active
active

Step 5 - Browse the web UI

Open http://<vm-public-ip>/ in a browser and sign in with admin and the password from Step 3. The home page lists the container image repositories hosted on this registry.

The Zot web UI registry home listing the hosted container image repositories

Select a repository to see its tags, size and metadata.

The Zot web UI repository view listing the tags of a pushed container image

Open a tag to inspect its manifest, layers and the vulnerability (CVE) scan results produced by the bundled search extension.

The Zot web UI image detail view showing the manifest and the vulnerability scan summary

Use the explore and search view to find repositories and images across the registry, filtered by operating system and architecture.

The Zot web UI explore and search view for finding repositories and images

Step 6 - Log in and push an image from a developer machine

From any machine with docker or podman installed, log in to the registry with the admin credentials, tag a local image for the registry, and push it. Replace <vm-public-ip> and <ZOT_ADMIN_PASSWORD> with your values.

With docker:

docker login <vm-public-ip> --username admin --password '<ZOT_ADMIN_PASSWORD>'
docker pull alpine:3.20
docker tag alpine:3.20 <vm-public-ip>/library/alpine:3.20
docker push <vm-public-ip>/library/alpine:3.20

With podman (identical flow):

podman login <vm-public-ip> --username admin --password '<ZOT_ADMIN_PASSWORD>'
podman pull docker.io/library/busybox:1.36
podman tag docker.io/library/busybox:1.36 <vm-public-ip>/library/busybox:1.36
podman push <vm-public-ip>/library/busybox:1.36

Because the image serves plain HTTP on port 80 until you add TLS (see Enabling HTTPS), docker/podman treat the registry as insecure. For a quick test add <vm-public-ip> to insecure-registries in /etc/docker/daemon.json (docker) or to [[registry]] insecure = true in /etc/containers/registries.conf (podman). In production, enable TLS and this is unnecessary.

Step 7 - Pull an image back and list the catalog

Pull the image you pushed back down on any client:

docker pull <vm-public-ip>/library/alpine:3.20

List every repository in the registry over the OCI catalog API (run on the VM):

PASS=$(sudo grep '^ZOT_ADMIN_PASSWORD=' /root/zot-credentials.txt | cut -d= -f2-)
curl -s -u "admin:${PASS}" http://127.0.0.1/v2/_catalog

Example output (your repositories will differ):

{"repositories":["library/alpine","library/busybox"]}

Step 8 - Push OCI artifacts with skopeo or oras

Zot is a generic OCI registry, so you can copy images with skopeo or push arbitrary OCI artifacts with oras:

skopeo copy --dest-creds admin:'<ZOT_ADMIN_PASSWORD>' \
  docker://docker.io/library/redis:7 \
  docker://<vm-public-ip>/library/redis:7

oras push <vm-public-ip>/cloudimg/notes:v1 \
  --username admin --password '<ZOT_ADMIN_PASSWORD>' \
  ./release-notes.txt:text/plain

Storage on a dedicated data disk

The OCI blob storage, configuration and the htpasswd authentication database live on a dedicated Azure data disk mounted at /var/lib/zot (rootDirectory in /etc/zot/config.json), kept separate from the OS disk so it can be resized independently and is re-provisioned with every VM. Confirm the mount and free space on the VM:

findmnt -no SOURCE,TARGET,FSTYPE /var/lib/zot
df -h /var/lib/zot | tail -1

Expected output (device name may differ):

/dev/sdc /var/lib/zot ext4
/dev/sdc         40G   24K   38G   1% /var/lib/zot

To grow registry storage, resize the data disk in the Azure Portal (or az disk update --size-gb), then grow the filesystem on the VM with sudo resize2fs /dev/disk/azure/scsi1/lun0.

Enabling HTTPS

Zot serves plain HTTP on port 80 through nginx. For production, terminate TLS at nginx with a certificate for your own domain. Point an A record at the VM's public IP, then install a certificate with certbot and the nginx plugin (prose - run on the VM, follow the interactive prompts):

Install certbot with sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx, then run sudo certbot --nginx -d your-domain.example.com. Certbot edits the nginx site to listen on 443 with your certificate and sets up automatic renewal. After TLS is in place, docker login and podman login no longer need the insecure-registry workaround.

Maintenance

  • OS updates: the image keeps Ubuntu's unattended-upgrades enabled, so security patches continue to land. Apply pending updates manually at any time with sudo apt-get update && sudo apt-get upgrade -y.
  • Service logs: sudo journalctl -u zot.service -f follows the registry log; sudo journalctl -u nginx.service for the proxy.
  • Restart: sudo systemctl restart zot.service nginx.service.
  • Garbage collection: Zot is configured with dedupe and gc enabled, so deleted manifests and unreferenced blobs are reclaimed automatically.
  • Rotating the admin password: generate a new bcrypt entry with sudo htpasswd -B /var/lib/zot/htpasswd admin and re-read it - Zot reads the htpasswd file at request time, so no restart is needed.

Support

This image is maintained by cloudimg with 24/7 support. Contact support@cloudimg.co.uk for assistance.