Hosting your first Private Docker Registry

By Gaurav Gahlot | February 16, 2019

This is the very first article on this blog about Docker. And I can’t tell you how excited I’m to write it. This is the very beginning of a whole new journey. Alright then, let’s get started.

In this article we will be talking about:

  • What is a Registry?
  • Running our first Private Registry
  • Interacting with 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, checkout DockerHub.

Let’s spin up our first registry container using the following command:

docker run -it -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:

ubuntu@docker:~$ docker run -it -p 5000:5000 --name registry registry:2.7

docker: Error response from daemon: driver failed programming external
connectivity on endpoint registry (2793fe2664b4f202c4ab4bb017c8073b812d643904e62426814f5aa60b44dfa5):
Bind for 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. 

docker run -it -p <new-port>:5000 --name registry registry:2.7

And this time things should workout.

Note that these steps are to setup a registry for testing purpose 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 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, 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

Before we push our first image I want you to note that we had started our registry container with -it options. This means that we can’t use the same terminal to interact with the registry. So, we have three options here:

  1. open up a new terminal for further interactions and use the current one to observe the logs
  2. press the keys ctl+pq (control + pq) which will leave our registry container running in detached mode and return terminal control
  3. stop the current instance by pressing the ctl+c keys and run the following commands:
# remove the stopped container named 'registry'
docker container rm registry 

# spin up new registry container in detached mode
docker run -d -p 5000:5000 --name registry registry:2.7</code></pre>

Now, let’s write a Dockerfile to create our own image. Here is mine, a simple one:

FROM busybox
LABEL Author="Gaurav Gahlot"
LABEL Version="v1"</code></pre>

We can now build our image using the command:

docker build -t localhost:5000/my-busybox .

If you are building the image on a host other than the one hosting our registry server, replace localhost with the IP of hostname of the registry host.

To make things clean, let’s add different tags to our image and push it to the registry:

docker tag localhost:5000/my-busybox localhost:5000/my-busybox:v1
docker tag localhost:5000/my-busybox localhost:5000/my-busybox:custom

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:

  "repositories": [

We can also list out different tags of an image, by making a tags list request, as shown below. This gives us a list of tags added to my-busybox as a JSON result:

# http://REGISTRY-HOST-IP:REGISTRY-PORT/v2/my-busybox/tags/list
  "name": "my-busybox",
  "tags": [

The V2 API exposes different endpoints that allow us to perform different operations on an image or interact with registry itself. You 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:

docker pull <registry-host-ip>:5000/my-busybox

Using default tag: latest
Error response from daemon: Get https://<registry-host-ip>: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.

docker info

Experimental: true
Insecure Registries:
Live Restore Enabled: false

As it can be seen in the output, the default registry for Docker is 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 about our insecure registry, we have to add a daemon.json file with an entry of our registry server.

# execute the following command as root user
nano /etc/docker/daemon.json

# add the following to daemon.json
   "insecure-registries": ["REGISTRY-HOST-IP:REGISTRY-PORT"]

# exit the nano editor with ctl+x
# as root user, restart the Docker daemon
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.

docker pull <registry-host-ip>:5000/my-busybox

Using default tag: latest
latest: Pulling from <registry-host-ip>:5000/my-busybox
697743189b6d: Pull complete 
Digest: sha256:061ca9704a714ee3e8b80523ec720c64f6209ad3f97c0ff7cb9ec7d19f15149f
Status: Downloaded newer image for <registry-host-ip>: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 the -it options.

docker run -it --rm --name my-container REGISTRY-HOST-localhost:5000/my-busybox top

Output of `top` command running in container

Congratulations!! We have setup our first registry server and have successfully pushed and pulled an image from it.


In this article, we have setup 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 purpose, it is not ready for production in any way.

comments powered by Disqus