Learning Docker – Registration, please

Welcome to my tutorials on how to leverage Docker to deploy your application! This is meant as much to help me learn, so it doubles as a “what worked for me” series.

In the previous lesson, we will saw how to use Docker for development and deployment, including how to make a full image that could be used for production based on your development environment and how to upgrade and revert a production container. We will also learned a couple different ways to clone a Docker volume, including some of the challenges of cloning a live volume.

This lesson, we will create a private Docker Registry. We will also use the OpenStack Swift storage driver to store your registry in a storage bucket. We will also enable SSL encryption using the Let’s Encrypt service and setup password authentication.

Up to this point, we have used the local computer to store your working images. This is fine for a personal development environment, but at some point, we need a dedicated place to store your images that can be accessed by other systems, and this is a requirement for production environments. This is even helpful if you are working exclusively in development, or even exclusively on your own computer. This is because, if done right, you have a means to use the registry as an image backup system, too.

To make this reality of a “backed up” registry possible, we are going to leverage the Docker Swift driver and mount a bucket to store your images. While you could use a cloud provider, there are other private solutions: my QNAP NAS system supports acting as an Swift and S3 storage provider, and so I am using that to store my images. (For the record, I tried this initially as S3 storage, but there was a protocol error. 😛 ) When I am done, I will have a means to store my images off computer and can survive a hard drive failing.

Docker has directions for creating a registry at https://docs.docker.com/registry/deploying/ and some configuration options for using the OpenStack drivers at https://github.com/docker/docker.github.io/blob/master/registry/storage-drivers/swift.md. We also cover some authentication options and setup SSL encryption. However, as of June 2020, the registry binary in the Docker image does not use the current version of the Let’s Encrypt ACME v2.0 protocol and has not worked since November 2019, so we’re going to compile a fresh copy for your image.

Let’s start there. Create a folder for this project, and run the following:

docker run --rm -v $(pwd):/mnt -it golang:alpine /bin/bash -c "apk update; apk upgrade; apk add --no-cache bash git openssh;go get github.com/docker/distribution/cmd/registry; mkdir /mnt/bin; cp -v /go/bin/registry /mnt/bin"

or on Windows, updating <Full Window Path>:

docker run --rm -v "<Full Windows Path>:/mnt" -it golang:alpine /bin/bash -c "apk update; apk upgrade; apk add --no-cache bash git openssh;go get github.com/docker/distribution/cmd/registry; mkdir /mnt/bin; cp -v /go/bin/registry /mnt/bin"

This sets up a go environment to compile the latest version of the registry executable and builds it for us.

With all the changes we need to make, we are now going to build your own custom registry image with your settings baked in. If we didn’t need the updated registry binary, we could instead pass in environment variables to configure the container. So now we need a couple things: a Dockerfile, a config file for the registry, and a password file. We also need to copy in the updated registry executable. Now let’s create your own version of the registry image. Create a Dockerfile:

FROM registry:latest
COPY ./bin/registry /bin/registry

And run:

docker build -t my_registry .

This gets us a stock-configured registry container with a fresh registry binary.

Let’s get a copy of the stock config file. The registry documentation at https://docs.docker.com/registry/configuration/ indicates the file is stored at /etc/docker/registry/config.yml. Let’s create a registry folder to hold your new config files, and make a copy of the one file in the registry folder:

docker run --rm my_registry cat /etc/docker/registry/config.yml > registry/config.yml

Or if on Windows:

docker run --rm my_registry cat /etc/docker/registry/config.yml > registry\config.yml

Now open the file in your text editor of choice (but remember to save with Unix newlines). The default settings are pretty sparse. We are going to add your settings for Swift storage, using Let’s Encrypt for an SSL certificate, and a password file. Set the config.yml to the following, correcting placeholders as needed:

version: 0.1
log:
  accesslog:
    disabled: true
storage:
  cache:
    blobdescriptor: inmemory
#  filesystem:
#    rootdirectory: /var/lib/registry
#    maxthreads: 100
#  s3:
#    accesskey: <accesskey>
#    secretkey: <secretkey>
#    region: US-East-1
#    regionendpoint: <S3 URL>
#    bucket: <bucket_name>
#    secure: true
#    v4auth: true
#    rootdirectory: /
  swift:
    username: <username>
    password: <password>
    authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth
    insecureskipverify: true    
    container: <bucket_name>
    rootdirectory: /
http:
  addr: :5000
  host: https://<dns_name>:5000
  tls:
    letsencrypt:
      cachefile: /etc/docker/registry/letsencrypt.json
      email: <your_email>
      hosts: [ "<dns_name>" ]
  headers:
    X-Content-Type-Options: [nosniff]
auth:
  htpasswd:
    realm: docker-registry
    path: /etc/docker/registry/htpasswd
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

I left two commented out storage drivers: the S3 driver (which I think would have worked if my NAS was behaving) and the filesystem driver. You may only have one storage driver configured per registry, but I wanted to share other options as well. The HTTP options should reflect the externally available DNS name for your computer; this is a requirement for Let’s Encrypt. Also, you will need for your registry to be available over teh Internet at port 443 for the initial certificate handshake to work. If you are using your home network, then you should be able to log into your home router and setup a TCP firewall rule connecting 443 from the Internet to your computer’s port 5000; directions on how to do this are beyond the scope of this tutorial. However, after the certificate is received, you can edit the firewall rule to use port 5000 instead of 443.

Next we will configure authentication. This allows you to password protect your registry. If you don’t plan on this being a web-accessible registry, you could skip this. I’m including this because I am trying to implement some security. Let’s create an htpassword file, replacing username and password with your choices:

docker run --rm httpd htpasswd -nbB username password >> registry/htpasswd

Or on Windows:

docker run --rm httpd htpasswd -nbB username password >> registry\htpasswd

Run the above command for as many users as you would like.

We can test everything using bind mounts to place your files where needed. On Linux:

docker run --rm -v $(pwd)/registry:/etc/docker/registry -p "5000:5000" --name registry my_registry

And on Windows:

docker run --rm -v "<Full Windows Path>/registry:/etc/docker/registry" -p "5000:5000" --name registry my_registry

Quickly run the following command to force the container to get an SSL certificate:

docker login https://<dns_name>

Once this is done and you can successfully log in, you can update your firewall rule, including deleting it. You will need the rule back when your cert expires in a year, though.

Now test out your login and ability to push files:

docker login <dns_name>:5000
docker image tag my_registry <dns_name>:5000/registry:latest
docker image push <dns_name>:5000/registry:latest

The process to push a file is to login to the registry (I think this only needs to be done once, until the password expires), then tag the image to the new registry, then to push the image with it’s new tag name.

At this point, it works for me. Now we can make your permanent image, and upload it to the registry for safe keeping. Lets update the Dockerfile with your two config files:

FROM registry:latest
COPY ./bin/registry /bin/registry
ADD ./registry/config.yml /etc/docker/registry/
ADD ./registry/htpasswd /etc/docker/registry/

Now rebuild your image:

docker build -t my_registry:latest .

Last, let’s create a docker-compose.yml file for a clean container definition:

version: "3.8"
volumes:
  letsencrypt_data: {}
#  registry_data: {}
services:
  registry-container:
    image: my_registry
    restart: always
    ports:
     - "5000:5000"
    volumes:
#     - "registry_data:/var/lib/registry"
     - "letsencrypt_data:/etc/docker/registry/letsencrypt.json"

Again, I left comments here in case you are using your local computer as registry storage. Uncomment these lines in order to build a named volume and to mount it for use with your registry. You don’t need this if you’re using the S3 or Swift storage drivers.

Now let’s stop your test container and launch docker-compose:

docker container stop registry
docker-compose up -d

Now at this point, the container is up, but your SSL certificate is on your local computer. You need to copy it back. Fortunately, there is a docker command to copy files into and out of the container:

docker container ps
docker cp ./registry/letsencrypt.json <container_name>:/etc/docker/registry

Let’s restart the container to get the SSL cert loaded:

docker-compose stop
docker-compose start

Now re-tag your newest my_registry image and push it. For safe keeping.

docker login <dns_name>:5000
docker image tag my_registry <dns_name>:5000/registry:latest
docker image push <dns_name>:5000/registry:latest

You will notice that some layers are already in your registry if you were using the Swift or S3 drivers. This is because the original layers of your image were uploaded to your external storage while we were testing, and so they were skipped.

To summarize, we created a private Docker Registry using the OpenStack Swift storage driver to store your registry in a storage bucket externally. We also enabled SSL encryption with help from Let’s Encrypt and setup password authentication.

For the next lesson, we will create a web-accessible MUD.

Tagged :