Developer Tools AWS

Docker Registry (Distribution) on AWS User Guide

| Product: Docker Registry (Distribution)

Overview

This guide covers the deployment and operation of a private Docker Registry on AWS using cloudimg AWS Marketplace images. The 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 distributes OCI and Docker container images.

The image installs the official prebuilt registry Go binary (pinned and checksum-verified at build) and runs it under systemd as the unprivileged registry user, so a working private registry endpoint is serving over HTTPS within minutes of launch.

Secure by design - not an open push target. The registry process itself binds to loopback 127.0.0.1:5000 only and is never exposed to the network. An nginx front end terminates TLS on port 443 and enforces HTTP Basic authentication on the registry API. There is no default password inside the image: a unique admin credential is generated on this instance's first boot, stored as a bcrypt htpasswd entry and in a root-only credentials file, and never baked into the image. Anonymous requests to the registry API are rejected with HTTP 401, so only the per-instance credential can push or pull.

Dedicated storage. Image blobs are written to a dedicated EBS data volume mounted at /var/lib/registry, separate from and independently resizable from the operating system disk. The storage backend can later be switched to an object store such as Amazon S3 for larger deployments.

What is included:

  • Docker Registry powered by CNCF Distribution 3.1 (official binary, pinned and checksum-verified at build), run under systemd as the unprivileged registry user (registry.service)

  • An nginx TLS reverse proxy (nginx.service) on port 443 enforcing HTTP Basic auth, with a :80 to :443 redirect and an unauthenticated /healthz probe

  • A per-instance admin credential and a per-instance self-signed TLS certificate (SAN covering the instance public IP), both generated on first boot and never baked into the image

  • A dedicated, resizable EBS data volume at /var/lib/registry for image blobs

  • Unattended security upgrades left enabled so the server keeps receiving patches

Registry service active, loopback-only listener

Prerequisites

  • Active AWS account, an EC2 key pair, a VPC and subnet in the target region

  • Subscription to the Docker Registry listing on AWS Marketplace

  • A security group allowing TCP 22 (admin) and, from the clients that will push and pull images, TCP 443 (registry) and TCP 80 (redirect and health probe)

Recommended instance type: m5.large (2 vCPU, 8 GB RAM). Light registry use runs comfortably; busy registries or large image sets can move to a larger instance type and a larger data volume.

Step 1: Launch from AWS Marketplace

Find Docker Registry on AWS Marketplace, published by cloudimg, and choose Continue to Subscribe, then Continue to Configuration and Continue to Launch. Select the Ubuntu 24.04 delivery option, your instance type (m5.large), your key pair, and a security group that allows TCP 22 for administration plus TCP 443 and TCP 80 from the client networks that will use the registry.

Step 2: Connecting to your instance

SSH in with your EC2 key pair as the login user for the OS variant you launched.

OS variant SSH login user Example
Ubuntu 24.04 ubuntu ssh -i your-key.pem ubuntu@<public-ip>
ssh -i your-key.pem ubuntu@<public-ip>

Step 3: First boot

On first boot the image resolves the instance public IP via EC2 instance metadata, regenerates a self-signed TLS certificate for this instance (with the public IP in the certificate SAN), generates a per-instance admin credential (bcrypt htpasswd), starts the registry and nginx, and proves that anonymous access is rejected while the credential is accepted. This completes within a minute.

Confirm both services are active, and that the registry binary is bound to loopback only while nginx serves the network:

sudo systemctl start registry.service nginx.service 2>/dev/null || true
sleep 2
sudo systemctl is-active registry.service nginx.service
sudo ss -tln | grep -E ':(5000|80|443) '

The registry and nginx services report active, the registry listens on 127.0.0.1:5000 (loopback only), and nginx listens on ports 80 and 443.

The unauthenticated health probe returns ok and is handled entirely by nginx (no registry round-trip), which makes it ideal for a load-balancer health check:

curl -s --cacert /etc/nginx/tls/registry.crt https://127.0.0.1/healthz
echo

Step 4: Retrieve your registry credentials

The per-instance credential lives in a root-only file. Read it over SSH:

sudo cat /root/registry-credentials.txt

The file holds registry.admin.user (admin) and registry.admin.pass for this instance, the HTTPS endpoint, and the path to the self-signed certificate. These values are unique to this instance and were generated on first boot - they are never baked into the image.

Step 5: Authentication is enforced

The registry is never an open push or pull target. An anonymous request to the registry API returns HTTP 401 with a Basic auth challenge, while a request with the per-instance credential returns HTTP 200. The command below reads the credential straight from the root-only file, so no secret is printed:

sudo systemctl start registry.service nginx.service 2>/dev/null || true
sleep 2
CA=/etc/nginx/tls/registry.crt
P=$(sudo grep '^registry.admin.pass=' /root/registry-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'anonymous /v2/:      HTTP %{http_code}\n' --cacert "$CA" https://127.0.0.1/v2/
curl -s -o /dev/null -w 'authenticated /v2/:  HTTP %{http_code}\n' -u admin:"$P" --cacert "$CA" https://127.0.0.1/v2/

The anonymous request prints 401 and the authenticated request prints 200.

Authentication enforced, no anonymous access

Step 6: Trust the certificate on your Docker client

The registry uses a self-signed certificate generated for this instance. On each client machine that will push or pull, trust it once by copying the certificate into the Docker certificates directory for the registry host. Copy /etc/nginx/tls/registry.crt off the instance (for example with scp), then, on the client, using the instance public IP as the registry host:

sudo mkdir -p /etc/docker/certs.d/<public-ip>
sudo cp registry.crt /etc/docker/certs.d/<public-ip>/ca.crt

For production, front the registry with your own domain and a CA-signed certificate instead, and this step is no longer needed.

Step 7: Log in, push and pull

From a client that trusts the certificate, log in with the per-instance credential and push an image. Replace <public-ip> with your instance's public IP and <password> with the registry.admin.pass value from Step 4:

echo '<password>' | docker login <public-ip> -u admin --password-stdin
docker pull alpine:3.20
docker tag alpine:3.20 <public-ip>/demo/alpine:1.0
docker push <public-ip>/demo/alpine:1.0
docker pull <public-ip>/demo/alpine:1.0

docker login returns Login Succeeded, and the push and pull transfer the image by content digest - a full authenticated round-trip against your private registry.

Authenticated docker login, push and pull

Step 8: Browse the catalog

The Distribution API exposes the repositories and tags stored in the registry. List them with the per-instance credential (read from the root-only file so no secret is printed):

CA=/etc/nginx/tls/registry.crt
P=$(sudo grep '^registry.admin.pass=' /root/registry-credentials.txt | cut -d= -f2-)
echo "Catalog:"
curl -s -u admin:"$P" --cacert "$CA" https://127.0.0.1/v2/_catalog
echo

The catalog returns the list of repositories pushed to this registry. Image blobs for these repositories are stored on the dedicated data volume.

Catalog and dedicated data-volume storage

Step 9: Storage and configuration notes

  • Dedicated data volume. Image blobs are stored on a separate EBS volume mounted at /var/lib/registry (an fstab entry keyed by filesystem UUID), so images survive and scale independently of the OS disk. Confirm the mount with:
df -h /var/lib/registry
  • Registry configuration lives at /etc/docker/registry/config.yml. It uses the filesystem storage driver with rootdirectory: /var/lib/registry, image deletion enabled, and scheduled upload purging. For larger deployments, switch the storage section to the s3 driver pointing at an Amazon S3 bucket, then restart with sudo systemctl restart registry.

  • TLS and authentication are handled by nginx at /etc/nginx/sites-available/cloudimg-registry. The certificate and key are in /etc/nginx/tls/, and the bcrypt credential file is /etc/nginx/.htpasswd (0640 root:www-data). Add more users with sudo htpasswd -B /etc/nginx/.htpasswd <username> and reload nginx.

  • Per-instance secrets. The plaintext credentials copy in /root/registry-credentials.txt is 0600 root:root. The credential and TLS certificate are generated on first boot and never baked into the image.

Step 10: Managing the services

sudo systemctl --no-pager --lines=3 status registry.service nginx.service

Restart the registry with sudo systemctl restart registry, and reload nginx after a configuration change with sudo systemctl reload nginx. Both services are enabled at boot.

Support

This image is published and supported by cloudimg. For deployment help, configuration questions, or issues, contact support@cloudimg.co.uk. Every cloudimg image ships with 24/7 support and fully patched operating systems with unattended security upgrades enabled.

This is a repackaged open source software product with additional charges for cloudimg support services. The CNCF Distribution registry is distributed under the Apache License 2.0. cloudimg is not affiliated with or endorsed by Docker, Inc. or the Cloud Native Computing Foundation. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.