Setup a Docker Registry

Introduction

If you’ve used Docker before, you’ve used a registry (whether you know it or not). Docker registries are where Docker images are stored and downloaded from. There are several well known Docker registries on the Internet, but our goal here is to be as self-sufficient as possible. Thus, we will host our own Docker registry. Any container images we want to use will be first loaded the local registry (pulled from an Internet registry) and then Nomad will load the images from the local registry. This will speed up re-downloading of images as tasks are deployed repeatedly. It will also prevent failures should internet access be lost or Internet registries become unavailable.

Environment

For the sake of this guide, assume there are three cluster nodes with addressing such as:

node1 - 10.0.3.101
node2 - 10.0.3.102
node3 - 10.0.3.103

Performing actions as root

The ideal security model dictates that interactively operating as root is incorrect, and that operations should run as a user, using ‘sudo’ to elevate permissions where necessary. Unfortunately, almost everything that needs to be done here will require ‘sudo’, so it will be faster to just become root and run everything as root:

sudo su -

Perform the following steps on one node until instructed otherwise

Pull the registry image

You will start by laying out the structure of the container data on the GlusterFS mount. Execute the following on one node (remember, GlusterFS will replicate these changes to all nodes):

cd /mnt/vagabond
mkdir docker && cd docker
mkdir _build && cd _build
mkdir registry && cd registry

At the time of the writing of this, ‘2.8.3’ was the latest tagged version of the Docker registry. You can see what the current tags are here: https://hub.docker.com/_/registry/tags

mkdir 2.8.3 && cd 2.8.3

Now you will create a script that will pull the Docker registry image, save it it to a local archive and then pull it into your local Docker library from the saved archive:

cat << EOF > build.sh
#!/bin/bash
export DOCKER_IMAGE=registry
export DOCKER_TAG=2.8.3
docker pull \${DOCKER_IMAGE}:\${DOCKER_TAG}
docker tag \${DOCKER_IMAGE}:\${DOCKER_TAG} local/\${DOCKER_IMAGE}:\${DOCKER_TAG}
docker image rm \${DOCKER_IMAGE}:\${DOCKER_TAG}
docker image save -o \${DOCKER_IMAGE}_\${DOCKER_TAG}.tar local/\${DOCKER_IMAGE}:\${DOCKER_TAG}
gzip \${DOCKER_IMAGE}_\${DOCKER_TAG}.tar
EOF
chmod u+x build.sh
cat << EOF > load.sh
#!/bin/bash
docker image load -i registry_2.8.3.tar.gz
EOF
chmod u+x load.sh
./build.sh

Perform the following steps on each node until instructed otherwise

Install the registry

The Docker registry will not run from Nomad, it is what one would consider a foundational service. Like Consul and Nomad, each node will run it’s own instance of the registry.

Load the container image for the registry service:

cd /mnt/vagabond/docker/_build/registry/2.8.3
./load.sh

Create the ‘docker-compose.yml’, assuming that there is no existing file:

cd /docker
cat << EOF > /docker/docker-compose.yml
version: '3'
services:
  registry:
    image: local/registry:2.8.3
    container_name: registry
    ports:
      - 169.254.254.254:8443:443
    volumes:
      - /docker/registry:/etc/docker/registry:ro
      - /mnt/vagabond/docker/registry/data:/var/lib/registry
    restart: unless-stopped
EOF

Create the configuration file for the registry service:

mkdir /docker/registry

Make sure you substitute your own domain name to reflect your internal domain.

cat << EOF > /docker/registry/config.yml
version: 0.1
log:
  fields:
    service: registry
storage:
  delete:
    enabled: true
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
http:
  addr: 0.0.0.0:443
  tls:
    certificate: /etc/docker/registry/wildcard.pem
    key: /etc/docker/registry/wildcard.key
  headers:
    X-Content-Type-Options: [nosniff]
    Access-Control-Allow-Origin: ['https://registry.home.digitaldann.net']
    Access-Control-Allow-Credentials: [true]
    Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
    Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
    Access-Control-Expose-Headers: ['Docker-Content-Digest']
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
EOF

Copy the wildcard certificates you issued:

cp /mnt/vagabond/ca/certs/wildcard.pem /docker/registry/wildcard.pem
cp /mnt/vagabond/ca/certs/wildcard.key /docker/registry/wildcard.key

Now start the registry service:

docker compose up -d registry

Make sure the registry resolves locally (again, make sure to substitute the domain name):

echo "169.254.254.254 registry.home.digitaldann.net" >> /etc/hosts

Some Notes

The registry service listens on 169.254.254.254, which is a virtual network interface that was created by the Ubuntu configuration playbook. This interface is useful so that containers can reach services specific to the local machine, and the interface address is the same across all nodes. Trying to reach the loopback interface (127.0.0.1) on the host from within a container will fail since each container also has a loopback adapter. If you want the registry to be accessible from outside the nodes, remove the IP address prefix on the port mapping in the compose file.

Also, note that the registry service is NOT running on the standard port 443 for HTTPS, but on an alternate port of 8443. This is to avoid conflict with reverse proxied applications on the standard port 443.

Conclusion

That’s all! Move on to the next part.