OctoPlasm


How to Setup a Private Docker Registry (on Ubuntu 20.04)

This is a quick guide on how to install a private docker repository on Ubuntu 20.04 server.

Most likely, if you clicked on the title, you already know what the Docker registry is. But just in case you don't, Docker registry is a store or repository for the docker build images similar to the Maven repository for Java libraries or Npm for the Javascript libraries.

You might also ask yourself why we need a private repository since there is already a Docker Hub or Amazon ECR. This could be for security reasons, to have more control over the registry and builds or simply because of requirements.

Prerequisites

All the commands are executed as a sudo user, so bear in mind that you might have to prepend sudo to all the below commands.

First, we need to update the Ubuntu package management tool:
apt-get update -y # -y = automatic yes
Install (certbot)[https://certbot.eff.org/] to get a free HTTPS certificate.
apt-get install certbot

Of course, for this, you will need a domain first, so alternatively, you could go with a self-signed certificate. A guide about the self-signed certificate is described here, but as you can see in the link, the ride is not so smooth.

Install Docker and Registry

apt-get install docker
docker pull registry

This will just pull the registry image. Before we run it we still need to generate the certificate and update the run configuration.

Create a Signed Certificate

certbot certonly -d '<*.yourdomain.com,yourdomain.com>' --manual --preferred-challenges DNS
Follow the instruction (add the DNS record to your domain provider: TXT _acme-challenge <secret hash>)

Once we have the generated keys, the Docker registry expects different formats, so we need to convert the keys with the following commands.

mkdir /etc/certs
cd /etc/letsencrypt/live/<your domain>/ ##  location of certbot/letsencyrpt certificate
cp privkey.pem domain.key
cat cert.pem chain.pem > domain.crt
chmod 640 domain.crt
chmod 640 domain.key
cp /etc/letsencrypt/live/<your domain>/domain.key /etc/certs/ 
cp /etc/letsencrypt/live/<your domain>/domain.crt /etc/certs/

Start Registry

Now that we have a certificate, we can start the docker registry. This will expose our registry publicly on https://<your domain>:5000/v2

docker run -d -p 5000:5000 --restart=always --name registry \
-v /etc/certs:/etc/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/etc/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/etc/certs/domain.key \
-e REGISTRY_HTTP_HOST=https://<your domain>:5000 \
-e REGISTRY_STORAGE_DELETE_ENABLED="true" \
registry:2

Testing (on the client machine - docker is required)

First we will need to copy over certificate to our Docker engine.
mkdir -p /etc/docker/certs.d/<your domain>:5000/
scp <user>@<domain>:/etc/certs/domain.crt /etc/docker/certs.d/<your domain>:5000/
Restart docker! Build and tag your new image (optionally, you could also pull existing image and retag it).

docker build -t <image name> .
docker tag <image name>:latest <your domain>:5000/<image name>
docker push <your domain>:5000/<image name>
docker pull <your domain>:5000/<image name>

Push and pull should succeed without any exceptions for the test to be successful.

Secure it with basic authentication

Since this registry is now publicly available, bear in mind that everyone can push or pull from it. To secure it additionally, we could update the ufw firewall and set up at least basic auth.

For basic auth, we need to generate the encrypted password and user, which is then imported/used by the registry.

mkdir /docreg
docker run --entrypoint htpasswd registry:2.7.0 -Bbn <user> <strong password> > docreg/htpasswd

This command will generate htpasswd file with a user and encrypted (Bcrypt) password inside. Here we specifically used version 2.7.0. It looks like there is a bug in 2.7.1, and the generation of password does not work as expected (It is ok, lost just an hour on this issue :P ).

Once that is done, we can update the docker run configuration and restart the image.

docker stop $(docker ps -aqf "name=registry")
docker rm $(docker ps -aqf "name=registry")

docker run -d -p 5000:5000 --restart=always --name registry \
-v /home/snap/docreg:/home/snap/docreg \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/home/snap/docreg/htpasswd" \
-v /etc/certs:/etc/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/etc/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/etc/certs/domain.key \
-e REGISTRY_HTTP_HOST=https://<your domain>:5000 \
-e REGISTRY_STORAGE_DELETE_ENABLED="true" \
registry:2

On the security note ... this option is not the safest so it worth considering authentication with nginx and setting up rate limiter on it ...

Test Basic Auth

Before, we can do push/pull we first need to login.

docker login -u <user> -p registry-sn-p! <your domain>:5000
docker push <your domain>:5000/<image name>
docker pull <your domain>:5000/<image name>

 

And that it is it. Now, after a day of banging my head against the wall and searching for typos ... it looks simple :)