Creating a private Docker registry
All Yours
During testing, a local Docker registry for your container images can be useful. Images stored locally within a mini-registry give you more control over them, with improvements in speed when pulling the images. Also, you might be working with an application that needs access to a registry for one reason or another, but you do not want to give the application your Docker Hub credentials.
In this article, I look at installing a private Docker registry on a local machine (Ubuntu LTS 18.04 with Linux Mint on top) for testing purposes.
On Your Marks
The documentation from Docker on private registries is really detailed, but every now and again a local setup has unusual issues. To begin, you need to make sure that the latest version of Docker Community Edition (Docker CE) is installed. I used the documentation online [1], which you can reference if you get stuck and need more help.
As the root user, run:
$ apt remove docker docker-engine docker.io containerd runc
This command removes all other package manager versions or manually installed versions of Docker, so you can ensure that you are definitely using the Docker CE version. The documentation offers some reassurance that any container-related files residing within the /var/lib/docker
directory are not deleted during this process.
Next, install the package manager tools you will need (some or all of these packages might already be present), then add Docker's key (success is denoted by a simple OK ), and check the key's fingerprint:
$ apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - OK $ apt-key fingerprint 0EBFCD88
Have a look at the second line of the final command's output (Listing 1) and make sure it is exactly the same as the trusted key.
Listing 1
Checking the Fingerprint
pub rsa4096 2017-02-22 [SCEA] 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 uid [ unknown] Docker Release (CE deb) <docker@docker.com> sub rsa4096 2017-02-22 [S]
Assuming you have AMD64 or x86_64 hardware (see the documentation for ARM options), run:
$ add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu$(lsb_release -cs) stable"
This command adds the Docker repository to your Apt package manager resources in the /etc/apt/sources.list.d/additional-repositories.list
file and does not necessarily produce output on success. Because I am using Linux Mint, that command won't quite do what I need, so instead of running that command, I create a file called /etc/apt/sources.list.d/docker.list
:
deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable
Finally, run the commands to install the latest Docker CE version:
$ apt update $ apt install docker-ce docker-ce-cli containerd.io
The output tells you that two packages will be installed: docker-ce and docker-ce-cli .
By updating your package manager sources, you now have the ability to update your version of Docker CE automatically, in exactly the same way you would other system packages, whenever a newer version becomes available.
Check that Docker Engine is installed and has started up as hoped with the docker ps
command.
The Sounds of Science
You can now proceed to install a private Docker registry. The process is relatively simple, but as with anything related to containers, it can be nuanced. The setup involves creating simple htaccess
credentials to provide a degree of control over which users can access the image registry. Also, you will create your own self-signed certificates so that the connection to the registry is encrypted and you will know for sure that you are connecting to a known resource.
You might be pleasantly surprised to discover that the installation of the registry component is uber-simple, and you might not be entirely surprised to discover that the clever people at Docker have created a container image to do so. Pull the image for the registry with the command in Listing 2, ensuring that you only use version 2 and not the earlier version with the :1 tag.
Listing 2
Pulling the Registry Image
$ docker pull registry:2 2: Pulling from library/registry cbdbe7a5bc2a: Pull complete 47112e65547d: Pull complete 46bcb632e506: Pull complete c1cc712bcecd: Pull complete 3db6272dcbfa: Pull complete Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d Status: Downloaded newer image for registry:2 docker.io/library/registry:2
The most basic command to start up the registry (do not run it just yet, because the command will be improved significantly),
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
shows that (just like Docker Hub) registries run off TCP port 5000, and you are exposing it on your underlying host from the container port with the same number. You are also assuming that if the container fails or the machine reboots, the container will always restart. Finally, you are giving it a name (i.e., registry in this case) that you can reference in scripts and use from other containers.
You could pay for certificates if you like and set up DNS to point at your registry, but I will use self-signed certificates. To begin, create a local directory on which to drop the certs:
$ mkdir certs $ openssl req -newkey rsa:4096 -nodes -sha256 -key out certs/registry.key-x509 -days 999 -out certs/registry.crt
During the process, choose either your local IP address or DNS name to enter in the Common Name option, and you can mostly ignore the other options by just hitting the Enter key. If you're unsure, you can use another terminal to get your IP address with the command:
$ ip a
In my case, it was 192.168.1.48. After running this openssl
command, you should have a registry.crt
certificate file and a registry.key
private key file (saved in the certs/
subdirectory), which you can use in your docker run
command. If for some reason you did not install OpenSSL, simply install the package on Debian derivatives with:
$ apt install openssl
Now you need to create basic authentication with htaccess
. The process is also super-simple. (I use the htpasswd
binary from the apache2
toolset to create the credentials files.) Although you can save the .htaccess
file anywhere, for ease, I save it in the certs/
directory starting with this command:
$ apt install apache2-utils
The output informs you that the following new packages will be installed: apache2-utils , libapr1 , and libaprutil1 . The command
$ htpasswd -Bbn chrisbinnie [password] > certs/htpasswd
creates a simple .htpasswd
file; you probably recognize this file from enabling basic authentication for the Apache2 web server and Nginx. If you look inside the file, you will see a username with the password encrypted by the strong BCrypt password hashing:
$ cat certs/htpasswd chrisbinnie:$2y$05$7tHGMpCa1fNUJNaSxAiKsOKB8.KtBzYILoZgZEapY8nl6QzL4
Now you are all set to create a private registry.
Communication
Log in to the registry just as you would with any other image container registry, such as Docker Hub. Your credentials are those added to your .htpasswd
file. The cautious among you would delete the line showing your password from your Bash history.
The more complex docker run
command is shown in Listing 3. After running the command, check to see whether the registry is accessible. If it does not start properly, try running the
Listing 3
Complex docker run
$ docker run -d -p 5000:5000 --restart=always --name registry -v "$(pwd)"/certs:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Log into the registry" -e REGISTRY_AUTH_HTPASSWD_PATH=/certs/htpasswd -v "$(pwd)"/certs:/certs -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key registry:2
docker logs registry
command. More often than not, the paths for the auth
and certs
are incorrect. Next, run the familiar command:
$ docker ps
In the STATUS column, the output shows the container has been running for a few seconds without restarting. Two important concepts to bear in mind are that, with this particular setup, your locally stored images are available to your Docker Engine as usual, and you also have an image repository that can be accessed via the registry container. At this point, you can log in to your new registry with the credentials you just set up, replacing the IP address with your own (Listing 4).
Listing 4
Logging In
$ docker login <IP_address>:5000 Username: chrisbinnie Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded
Now that you have logged in to your registry, you should save a container image to the registry to test it. Be warned that you really need to understand the naming convention of container images at this point, so if you are new to containers, copy these commands exactly (altering them slightly to match your IP address or DNS name).
The command in Listing 5 lets you see which images are currently downloaded to your local Docker Engine. To prove that the images in your registry are different from those stored locally, download an image from Docker Hub, rename it (with the tag
command), and upload it (with a push
command) to your new registry.
Listing 5
See Current Images
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry 2 2d4f4b5309b1 2 months ago 26.2MB
It's important to use the container image's full name. For example, even from Docker Hub, an image's real name isn't (e.g., in the case of the Redis container) just redis:latest , but more like:
<hub-user>/<repo-name>:<tag>
On Docker Hub, the full name of popular images is abstracted away from the user for convenience, but for every other registry, it is really important to use the full name.
In this example, the full name of an image (again, using redis:latest as an example) would look like:
192.168.1.48:5000/redis:latest
To prove that your registry works as hoped, start by pulling redis from Docker Hub:
$ docker pull redis
That pull takes a bit longer than the local registry image pull because it is a larger image. Next, check that it is present locally (Listing 6). You can see that redis
is present and is now stored locally. Now rename the image with a tag
command,
Listing 6
Check redis Pull
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE redis latest 84c5f6e03bf0 45 hours ago 104MB registry 2 2d4f4b5309b1 2 months ago 26.2MB
$ docker tag redis:latest 192.168.1.48:5000/redis:latest
and check locally again for two images with the same hash ID (Listing 7).
Listing 7
Check for Renamed Image
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE 192.168.1.48:5000/redis latest 84c5f6e03bf0 45 hours ago 104MB redis latest 84c5f6e03bf0 45 hours ago 104MB registry 2 2d4f4b5309b1 2 months ago 26.2MB
Next, push the image with the longer name to the new registry (Listing 8). The output shows that you have written to your registry, as hoped.
Listing 8
Push Renamed Image
$ docker push 192.168.1.48:5000/redis:latest The push refers to repository [192.168.1.48:5000/redis] 2e9c060aef92: Pushed ea96cbf71ac4: Pushed 47d8fadc6714: Pushed 7fb1fa4d4022: Pushed 45b5e221b672: Pushed 07cab4339852: Pushed latest: digest: sha256:02d2467210e76794c98ae14c642b88ee047911c7e2ab4aa444b0bfe019a41892 size: 1572
For a final test, delete the images stored locally (i.e., the redis images) with the commands,
$ docker rmi redis:latest $ docker rmi 192.168.1.48:5000/redis:latest
and check that they are no longer present locally (Listing 9). You can see that no redis images are visible to Docker Engine locally. Now you can try and pull directly from your registry with the command in Listing 10. The results look promising, so check to see what is visible to Docker Engine (Listing 11).
Listing 9
Confirm Removed Images
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry 2 2d4f4b5309b1 2 months ago 26.2MB
Listing 10
Pull from Local Registry
$ docker pull 192.168.1.48:5000/redis:latest latest: Pulling from redis d121f8d1c412: Pull complete 2f9874741855: Pull complete d92da09ebfd4: Pull complete bdfa64b72752: Pull complete e748e6f663b9: Pull complete eb1c8b66e2a1: Pull complete Digest: sha256:02d2467210e76794c98ae14c642b88ee047911c7e2ab4aa444b0bfe019a41892 Status: Downloaded newer image for 192.168.1.48:5000/redis:latest 192.168.1.48:5000/redis:latest
Listing 11
Current Registry State
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE 192.168.1.48:5000/redis latest 84c5f6e03bf0 45 hours ago 104MB registry 2 2d4f4b5309b1 2 months ago 26.2MB
You have proven that images are abstracted away from your local storage that Docker Engine normally uses and do indeed exist within the registry's storage.
Buy this article as PDF
(incl. VAT)