In the previous part, we’ve learned how to utilize Docker Compose to tie multiple containers together with a single command. We’ve added a MySQL database to our ASP.NET Core application and successfully run it.
Before proceeding further, we need to learn more about image management and distribution. There are several ways to do that, whether locally or in the cloud, so you should definitely know these concepts before starting with continuous integration and application deployment.
In this part, we are going to learn the difference between a Docker registry and a Docker repository and how to persist the changes we made to our images. We’re also going to learn more about Docker Hub and how to make our own local Docker registry.
To see the complete overview of the series, go to the Docker Series page.
The starting point source code for this part can be found on the docker-series-docker-compose-end branch of our docker-series repo on GitHub.
Let’s see what we are going to go learn in this article:
- Difference Between Docker Repository and Docker Registry
- More About Docker Hub
- Creating a Local Docker Registry
- Pushing images to a Local Docker Registry
- Use Cases for Local Docker Registry
So let’s start off with clearing some concepts up.
Difference Between Docker Repository and Docker Registry
Besides sounding similar these terms can sometimes cause confusion so it seems appropriate to start out by explaining what each one means.
Docker Registry (Docker Trusted Registry – DTR) is an enterprise-grade storage solution for Docker images. In other words, it’s an image storage service. Think about GitHub, but for Docker Images.
We can use one of the existing and well-established cloud registries like Docker Hub, Quay, Google Container Registry, Amazon Elastic Container Registry or any other. We can also make our own registry and host it locally. We’re going to learn how to do that later on in this post.
One docker registry can contain many different docker repositories.
Docker Repository is a collection of Docker images with the same name and different tags. For example, the repository we’ve used several times so far, mcr.microsoft.com/dotnet/core/aspnet
repository, has many different images in it.
Each image has it’s own tag. For example, for the entire series, we’ve been using the mcr.microsoft.com/dotnet/core/aspnet:3.1
image which contains ASP.NET Core runtime with version 3.1. We can choose which one we want to pull by typing docker pull image-name:tag
similar to GitHub repo and commits. We can go back to whichever commit we want and pull it to the local machine.
That’s putting it in very simple terms. But now that we cleared the air around these terms we can proceed to the next section.
More About Docker Hub
As we’ve mentioned, Docker Hub is just one of the registry providers. And a good one at that. We can find all sorts of images over there and push our own. We can create unlimited public repositories and one private repo free of charge. If you need more private repositories, you can choose one of the Docker Hub monthly plans.
But that’s not all it does.
Besides providing a centralized resource for image discovery and distribution, Docker Hub’s functionality extends to:
- Automated builds of images on source code changes and parallel builds
- Webhooks on image creation and push
- Groups and organizations management
- GitHub and BitBucket integration
You can create your own account on Docker Hub right now and try it out.
To push the image from the local machine to Docker Hub we need to type docker login
and enter the credentials of your account in the prompt. After that, you can easily push the image by typing docker push accountname/imagename:tag
.
Easy as that.
If we don’t specify the tag Docker will apply the :latest
tag to it.
If we want to pull the image from the Docker Hub to the local machine, we need to type docker pull accountname/imagename:tag
. The same rule applies here. If you don’t specify the tag, you are going to pull the image tagged :latest
.
Let’s move on.
Creating a Local Docker Registry
Docker Hub is super neat and very intuitive and offers a great deal of functionality for free.
But what if we need more privacy? Or our client wants to use its own server.
If that’s the case, we can make our own Docker Registry.
So how do we do that?
Well, we can set up the registry in two different ways:
- directly with the Docker command
- using Docker compose
Creating a local registry using docker is pretty straightforward and shouldn’t be such a big deal if you followed the docker series so far. The registry repository is located on the Docker Hub here. (heh, registry repository) Aren’t you glad now that we talked about the differences between these terms 🙂
So if we want to set up the local registry we can type:
docker run -d -p 50000:5000 --restart always --name my-registry registry:latest
Now we can navigate to
http://localhost:50000/v2/_catalog
and see for yourself that your registry is up and running and that you have no repositories pushed to it.
You should be able to see something like this:
// 20191218210805 // http://localhost:50000/v2/_catalog { "repositories": [ ] }
The same thing can be done with Docker Compose that we introduced in the previous part of the series.
Let’s navigate to the root of our solution and make a new folder Infrastructure
and in it, another one called Registry
. In the Registry
folder, we are going to create a docker-compose.yml
file:
version: '3.0' services: my-registry: image: registry:latest container_name: my-registry volumes: - registry:/var/lib/registry ports: - "50000:5000" restart: unless-stopped volumes: registry:
We defined the same thing we did with Docker command but with some additional goodies. We added a volume to persist our data and defined the restart policy as
unless-stopped
, to keep the registry up unless it is explicitly stopped.
Now we can stop the registry we’ve spin up before with docker stop my-registry
and docker rm my-registry
to remove the attached container.
After that run docker-compose up -d
in the /Infrastructure/Registry folder.
And guess what?
We have the exact same registry we spin up before and we can access it by navigating to http://localhost:50000/v2/_catalog
.
We should also add the entry to the windows hosts file so we can use my-registry instead of localhost (usually at C:/Windows/system32/drivers/etc/hosts):
127.0.0.1 my-registry
Pushing Images to a Local Docker Registry
Ok, so now we have our own local registry. Let’s push some images to it.
If you’ve followed the series you have the codemazeblog/accountowner:runtime
image on your local machine. If not, please refer to part 4 of the series where you can learn how to create images with Docker Compose.
To push the image to local registry we need to tag it appropriately first:
docker tag codemazeblog/accountowner:runtime my-registry:50000/codemazeblog/accountowner:runtime
And then we can push it to the registry:
docker push my-registry:50000/codemazeblog/accountowner:runtime
Now, if we browse to
http://localhost:50000/v2/_catalog
we will see that our repository list is no longer empty. We successfully added our image to the local registry!
Moreover, if we navigate to http://localhost:50000/v2/codemazeblog/accountowner/tags/list
we’ll see:
// 20191218210646 // http://localhost:50000/v2/codemazeblog/accountowner/tags/list { "name": "codemazeblog/accountowner", "tags": [ "runtime" ] }
Here we can find the list of all the available tags of our image.
To test this out we can remove the local image with docker rmi my-registry:50000/codemazeblog/accountowner:runtime
. Now you can pull the image from the local registry by typing docker pull my-registry:50000/codemazeblog/accountowner:runtime
and behold, once again, the image is on your local machine!
That wasn’t too hard, was it?
There is one important note here. Pushing images to the insecure registries is not recommended and we can easily be the target of the MITM attack if we are not careful.
We’ve talked about HTTP Security and MITM in part 5 of our HTTP Series so if you’re not familiar with it, that’s a good starting point.
Setting a certificate for the registry is beyond the scope of this article and will be described later. For now, you can find more info on how to set a certificate for a local registry here.
So be careful and secure your registries if you want to use them in production.
Let’s wrap this up with some examples of when we might to set up a local Docker registry.
Use Cases for Local Docker Registry
Now that you know pretty much everything you need to run a local registry, you might wonder: “But why should I use a local registry when I have all those nice options available?”.
There are a few reasons for that:
- Total control of our registry and repositories
- We need to set up a local network and to easily distribute images throughout it, to save the bandwidth
- We have a closed network without internet access
- Setting up a local CI build server that uses that registry
- We don’t want to pay some crazy plans to cloud providers to host our repositories
- Many, many others
So there you go.
Conclusion
In this part, we’ve tackled the concepts of the Docker registry and the Docker repository. We’ve learned what the differences between those are and we’ve learned more about a cloud registry service Docker Hub.
We have also learned how to create our own registry locally and how to push images to it, and pull images from it.
Local registries are neat and have several practical use cases. We gave you some ideas on how you might apply your Docker registry.
Now that we know all this, we can easily transition to the next part where we are going to talk about Continuous Integration with Docker.
The complete source code with the modifications we’ve made in this part can be found on the docker-series-local-registry-end branch of the docker-series repo on GitHub.