In a previous post, we used Docker to build a container image that has our application and run it locally. In this post we will go through applying DevOps to this application.
The DevOps pipeline should go like this:
- Build: Developers define builds that run automatically to build their software and package it in a container image. Already covered in last post.
- Ship: Tag this image (we will talk shortly about tags) and push it to a Docker registry (we will talk about that too) either the public Docker Hub or a private registry. Thus making it available for others to discover and deploy it.
- Run: IT Operations guys, define automated releases that could be triggered by registry updates or manually trigger, to pull the new image from the registry and run it as a container on any server or environment.
In this post we will focus on the Ship part, and in a future post we will cover the Run part using Kubernetes.
What are Docker Tags?
Previously we when we referenced container images we used terms like:
In this case we are requesting an image that is:
- From Docker Hub since we didn’t specify a registry before “microsoft”, otherwise it should be written first like: myregistry:5000/dotnet
- From a user named: microsoft
- A repository named: dotnet
- An image with tag: 2.1-aspnetcore-runtime
Note that this tag specify a specific version (2.1) with specific components (aspnetcore-runtime). In some other cases tags can also specify base OS like: stretch or jessie. Look at the other tags available in this repo: microsoft/dotnet
In this repo, they are using semantic versioning, for example there are other tags like 2 and 2.1.401 that refer to the same image. This way you can reference a very specific minor version which means even if microsoft produced a newer version you will not use it. Or you can reference only version 2 which will get the latest version under major version 2.
Note that when you download an image you only choose one tag (not multiple) that’s why tags are strings that can be composed of all the options described above.
Also when you don’t specify a tag, Docker will always use a tag named “latest”. From the name of this tag, it should refer to the latest stable version with default configurations/component. But it’s the responsibility of the repo owner to manage which image gets this tag.
How to create my private Docker Registry
Even if you will publish your docker images to the public on Docker Hub. You will probably need to develop and test them privately, apply your DevOps pipeline privately, and then when you reach a stable version, you push it to the public registry.
If you want to try everything on your development server, luckily there is already a container image that you can pull and it will be this registry. For the sake of simplicity here is the easiest command to run a registry on your local machine:
docker run -d -p 5000:5000 --restart=always --name registry registry:2
This will pull the latest image from a repo named “registry” on Docker Hub and will run it, forward port localhost:5000 to port 5000 inside the container.
Later, you can check how to secure this registry.
Push an image to our private registry
Now lets download a sample image from Docker Hub, or you can use your own built image name:
docker pull microsoft/dotnet-samples
Tag the image as
localhost:5000/mysample. This creates an additional tag for the existing image. When the first part of the tag is a hostname and port, Docker interprets this as the location of a registry, when pushing.
docker tag microsoft/dotnet-samples localhost:5000/mysample
Push the image to the local registry running at
docker push localhost:5000/mysample
Now you can open: http://localhost:5000/v2/_catalog from browser to see that your repository is created.
To add another tag to same image and push it:
docker tag localhost:5000/mysample localhost:5000/mysample:1.0docker push localhost:5000/mysample:1.0
You can check the available tags by browsing: http://localhost:5000/v2/mysample/tags/list
My approach for DevOps tags
Till now we shipped our container from development machine or build server to a registry that is available to ITOps who can download to run on servers.
Also we provided a way to version/tag these containers in a way that allow ITOps to identify which version they want to use.
My approach is to tag an image with version and branch name. So in a gitflow configuration where we have master branch for stable version and develop branch for development version, I generate a tag “develop” for the latest version of the develop branch + a tag “master” for the latest version of the master branch + tags for version numbers like “1.0.5” with semantic versioning. This way ITOps can configure the development environment to always pull from “develop” tag, staging environment to always pull from “master” tag and if they need to deploy an older specific version they can pull if from the version tags.
This is my approach that worked for me, just take it as a sample, but according to your plans and how you deploy you should create your own approach.