1. Overview

Docker is a software platform that works at OS-level virtualization to run applications in containers. One of the unique features of Docker is that the Docker container provides the same virtual environment to run the applications. CI/CD tools can also be used to automatically push or pull images from the registry for deployment on production.

In this tutorial, we’ll learn to understand the use of public and private Docker registries. We’ll also set up a private Docker registry. Additionally, we will push a Docker image to a private Docker registry and then pull the image from the same registry.

2. Private and Public Docker Registries

Docker provides the support for creating, storing, and managing the Docker images on a private server. Additionally, Docker also has a free public registry. The Docker Hub can host our images, but they will be publicly available. In most cases, images contain all the code and configuration needed to run an application. In that case, we can either use a Docker Hub private account or set up a private Docker registry on a machine.

The Docker Hub private account is paid, and it’s an expensive option for storing multiple images in the cloud. While the private Docker registry setup is free, all the commands to access the images from a private registry are simple and almost identical to those in Docker Hub. Using a private registry, we can balance the load, customize the authentication and logging, and make many more configuration changes. It creates a customized pipeline that helps store images in a personal location. Here, we’ll briefly cover how to manage images privately on a server.

A typical Docker image contains the application code, installations, configurations, and required dependencies. A Docker image usually consists of multiple layers. We can also push those layers to the private or public registry. Additionally, we’ll examine some options for security and storage that will allow us to customize the configuration. Using these, we can manage images securely and pull and push them quickly and securely.

3. Set up a Private Registry

We can reduce build times by centralizing the container images in private or public registries. We can also download a compressed image from a registry containing all of the application’s components in a bundled form, rather than installing different dependencies on different environments. To set up a private Docker registry, we first need to make changes in the default configuration of the Docker daemon.

3.1. Configure a Private Docker Registry

In Docker, we can set up a registry by running a container of a registry image. Before we move forward, let’s first update the default configuration of our Docker install.

Add the following configuration in the /etc/docker/daemon.json:

{
    "insecure-registries":[
        "localhost:5000"
    ]
}

In the above JSON, we’ve added localhost with port 5000 in the “insecure-registries” property. To apply the above changes, let’s reload the Docker daemon using the command line:

$ sudo systemctl daemon-reload

Now, we’ll restart the Docker service:

$ sudo systemctl restart docker

So far, we have successfully configured the private registry.

3.2. Run a Private Docker Registry

To run a private registry, we have to pull a registry image stored on the public Docker Hub:

$ docker pull registry
Using default tag: latest
latest: Pulling from library/registry
2408cc74d12b: Pull complete 
...
fc30d7061437: Pull complete 
Digest: sha256:bedef0f1d248508fe0a16d2cacea1d2e68e899b2220e2258f1b604e1f327d475
Status: Downloaded newer image for registry:latest
docker.io/library/registry:latest

We can also pull a specific version of the registry. Let’s now verify the registry image using the docker images command:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              773dbf02e42e        21 hours ago        24.1MB

Now, let’s run a Docker container using the registry image:

$ docker run -itd -p 5000:5000 --name baeldung-registry registry
e2d09cd3a5ef9c88e17e0393f7125b6eeffad175fa0ce69fa3daa7803a0b3067 

The internal server of the baeldung-registry container uses port 5000. Therefore, we exposed the 5000 port on the host:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
e2d09cd3a5ef        registry            "/entrypoint.sh /etc…"   3 minutes ago       Up 2 minutes        0.0.0.0:5000->5000/tcp   baeldung-registry

The above command confirms that the registry is up and running.

4. Push an Image to the Private Registry

To push an image to the private registry, let’s first pull the latest centos image from the public Docker registry:

$ docker pull centos
Using default tag: latest
latest: Pulling from library/centos
a1d0c7532777: Pull complete 
Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177
Status: Downloaded newer image for centos:latest
docker.io/library/centos:latest

Here, we’ve pulled a sample Docker image that we can push to the Docker private registry. First, we’ll tag the centos image, and later push it to the private docker registry. Here, we’ll tag it to localhost:5000/baeldung-centos:

$ docker tag centos:latest localhost:5000/baeldung-centos

Now, let’s check out all the images on the host machine:

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             SIZE
registry                            latest              773dbf02e42e        22 hours ago        24.1MB
localhost:5000/baeldung-centos   latest              5d0da3dc9764        8 months ago        231MB
centos                              latest              5d0da3dc9764        8 months ago        231MB

Here, we can see that imageId 5d0da3dc9764 has two different repositories. The tags in Docker are similar to symbolic links. To remove an image, we must either delete the imageId or explicitly delete both the tags.

Let’s check out the command to push the image to the docker private registry:

$ docker push localhost:5000/baeldung-centos
The push refers to repository [localhost:5000/baeldung-centos]
74ddd0ec08fa: Pushed 
latest: digest: sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc size: 529

With the above command, we have successfully pushed the baeldung-centos image to the private registry, which is locally set up on port 5000. Similarly, we can also store multiple images in the private registry.

5. Pull an Image From the Private Registry

The command to pull an image from a private registry is similar to pulling an image from Docker Hub. Here, first, we’ll remove all the images with imageId 5d0da3dc9764:

$ docker rmi 5d0da3dc9764

Let’s check out all the images stored on the host machine:

$ docker-registry]# docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
registry                         latest              773dbf02e42e        22 hours ago        24.1MB

We can see that images with imageId 5d0da3dc9764 have been removed. Let’s check out the command to pull an image from the private Docker registry:

$ docker pull  localhost5000/baeldung-centos
Using default tag: latest
latest: Pulling from baeldung-centos
a1d0c7532777: Pull complete 
Digest: sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc
Status: Downloaded newer image for localhost:5000/baeldung-centos:latest
localhost:5000/baeldung-centos:latest

The above command will pull the baeldung-centos image from the private registry.

6. Set up Authentication for a Private Registry

Docker allows us to store the images locally on a centralized server, but sometimes, it’s necessary to protect the images from external abuse. In that case, we’ll need to authenticate the registry with the basic htpasswd authentication.

Let’s first create a separate directory to store the Docker registry credentials:

$ mkdir -p Docker_registry/auth

Next, let’s run an httpd container to create a htpasswd protected user with a password:

$ cd Docker_registry &&

The above command will create a user with an htpasswd authenticated password. The details of the credentials are stored in the auth/htpasswd file.

Now, let’s run the same Docker registry container using the auth/htpasswd authentication file:

$ docker run -itd \
  -p 5000:5000 \
  --name registry \
  -v "$(pwd)"/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry
  3a497bafed4adb21a5a3f0b52307b4beaa261c6abe265e543cd8f5a15358e29d

Since the Docker registry is running with the basic authentication, we can now test the login using:

$ docker login  localhost:5000 -u baeldung-user -p baeldung
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
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

Once successfully logged in to the Docker registry, we can both push and pull images in the same way we discussed above.

7. Conclusion

This tutorial demonstrated how to create our own private Docker registry and push a Docker image.

First, we set up a private registry. Later, we pushed and pulled images to and from the registry. Lastly, we enabled the authentication in the private Docker registry.