How To Setup A Docker Private Registry🤷‍♂️

In this article, we will learn how to set up a docker private registry. A registry is a stateless, highly scalable server-side application that stores and lets you distribute Docker images.

Introduction

 
In this article, we will be talking about -
  • What is a Registry?
  • Running our first Private Registry
  • Interacting with the Registry
  • Pushing an image
  • Pulling the new image

What is a Registry? 

 
A registry is a stateless, highly scalable, server-side application that stores and lets you distribute Docker images. At a high level, a registry is a collection of different repositories which contain our images. These images have different tags. We generally use a private registry when we want to -
  • keep control of the distribution of images
  • control where the images are stored
  • integrate image storage and distribution tightly into your in-house development workflow

Running our first Private Registry

 
Before we start to deploy a registry, ensure that Docker is installed on the host machine. A registry server is based on the registry image.
 
To get more details about the image, check out DockerHub. Let's spin up our first registry container using the following command.
  1. docker run -d -p 5000:5000 --name registry registry:2.7  
If you don't get any errors (which you probably will not), then you should see something like the following.
 
Running the first Registry Server
 
Just in case, there is another container or process using port 5000 you might get an error like the below one.
  1. ubuntu@docker:~$ docker run -d -p 5000:5000 --name registry registry:2.7  
  2.   
  3. docker: Error response from daemon: driver failed programming external  
  4. connectivity on endpoint registry (2793fe2664b4f202c4ab4bb017c8073b812d643904e62426814f5aa60b44dfa5):  
  5. Bind for 0.0.0.0:5000 failed: port is already allocated.  
To fix that, all we need is to change the port from 5000 to something else, like 8000 or whichever is available. And this time, things should work out.
  1. docker run -it -p <new-port>:5000 --name registry registry:2.7  
Note that these steps are to set up a registry for testing purposes only. A production-ready registry must be protected by TLS and should ideally have an access-control mechanism. 
 
Now that our registry is up and running, we can use the Docker Registry HTTP API V2 to interact with our running instance. We will be using this API to get the list of repositories and list of tags of a particular repository. For details on Docker Registry HTTP API, please check the docs.
 
So, let's open up a browser, and go to http://localhost:5000/v2/_catalog/ and what we get is a list of repositories returned from our registry instance as a JSON response. Note that if you are not hosting the registry locally, you need to use the IP address of registry host instead of localhost. For instance, http://13.233.122.144:5000/v2/_catalog/ and you should see something like:
 
Repositories in our Registry
 
Notice that our list of repositories is empty. This is because we have not yet pushed any image to our registry. Let's do that next.
 

Pushing a Docker Image

 
Let's write a Dockerfile to create our own image. Here is mine, a simple one,
  1. FROM busybox  
  2. LABEL Author="Gaurav Gahlot"  
  3. LABEL Version="v1"  
We can now build our image using the command,
  1. docker build -t localhost:5000/my-busybox . 
Note that, if you are building the image on a host other than the one hosting our registry server, replace localhost with the IP of the hostname of the registry host. To make things clean, let's add different tags to our image. We can now push the image to our registry, using the following command,
  1. $ docker tag localhost:5000/my-busybox localhost:5000/my-busybox:v1  
  2. $ docker tag localhost:5000/my-busybox localhost:5000/my-busybox:custom  
  3.   
  4. $ docker push localhost:5000/my-busybox  
How do we verify that the push was successful? Well, we have got the Docker Registry HTTP API V2 that is used to interact with the registry server. Let's see that next. 
 

Interacting with Registry Server

 
To interact with the registry server we can use the Docker Registry HTTP API V2. Open a browser and go to http://REGISTRY-HOST-IP:5000/v2/_catalog/ . If you are hosting at a different port, then use that port instead of 5000. What we get is a list of repositories as a JSON response, and it should look like,
  1. // http://REGISTRY-HOST-IP:REGISTRY-PORT/v2/_catalog  
  2. {  
  3.   "repositories": [  
  4.     "my-busybox"  
  5.   ]  
  6. }  
We can also list out different tags of an image, by going to the URL http://REGISTRY-HOST-IP:5000/v2/my-busybox/tags/list. This gives us a list of tags added to my-busybox as a JSON result,
  1. // http://REGISTRY-HOST-IP:REGISTRY-PORT/v2/my-busybox/tags/list  
  2. {  
  3.   "name""my-busybox",  
  4.   "tags": [  
  5.     "custom"  
  6.     "latest",  
  7.     "v1"  
  8.   ]  
  9. }  
The V2 API exposes different endpoints that allow us to perform different operations on an image or interact with the registry itself. You can read more about it from the Docs
 

Pulling an Image

 
In order to understand the flow, we will now switch to another host that has Docker installed on it and can communicate with the registry host. On the second host, try to pull the "my-busybox" image from the registry and you should get an error as shown below.
  1. $ docker pull :5000/my-busybox  
  2.   
  3. Using default tag: latest  
  4. Error response from daemon: Get https://:5000/v2/: http: server gave HTTP response to HTTPS client  
In order to understand the reason for the above error, let's run the docker info command and notice the Registry and Insecure Registries section, at the end. 
  1. $ docker info  
  2.   
  3. ...   
  4.   
  5. Registry: https://index.docker.io/v1/  
  6. Labels:  
  7. Experimental: true  
  8. Insecure Registries:  
  9.  127.0.0.0/8  
  10. Live Restore Enabled: false  
As it can be seen in the output, the default registry for Docker is docker.io (Docker Hub). Docker first tries to search for an image in the local filesystem and if the image is not available, it goes looking for it at the Docker Hub. To make Docker aware of our insecure registry, we have to add a daemon.json file with an entry of our registry server.
  1. // execute the following command as root user  
  2. $ nano /etc/docker/daemon.json  
  3.   
  4. // add the following to daemon.json  
  5. {  
  6.    "insecure-registries": ["REGISTRY-HOST-IP:REGISTRY-PORT"]  
  7. }  
  8.   
  9. // exit the nano editor with ctl+x  
  10. // as root user, restart the Docker daemon  
  11. $ service docker restart  
Now, if we run the docker info command again and notice the Insecure Registries section, it will have an entry for our registry server.
 
Let's try to pull the image again, and it will be successful.
  1. $ docker pull :5000/my-busybox  
  2.   
  3. Using default tag: latest  
  4. latest: Pulling from :5000/my-busybox  
  5. 697743189b6d: Pull complete   
  6. Digest: sha256:061ca9704a714ee3e8b80523ec720c64f6209ad3f97c0ff7cb9ec7d19f15149f  
  7. Status: Downloaded newer image for :5000/my-busybox:latest  
We can now spin-up a container using the image we just pulled. Here I'm executing the top command in container and piping its output to my standard output with option -it.
  1. docker run -it --rm --name my-container REGISTRY-HOST-localhost:5000/my-busybox top  
 
Congratulations!! We have set up our first registry server and have successfully pushed and pulled an image from it. 
 

Conclusion

 
In this article, we have set up an insecure Docker registry and we have also seen how we can push and pull images from it. While this registry works well for testing purposes, it is not ready for production in any way. In upcoming articles, we will see how we can improve our registry to store data in a named volume and secure our registry with basic authentication.