As we get more acquainted with Docker, let's explore a little bit more what image tags are all about, even if we have discussed them before. All told, a tag is essentially a label you apply to your Docker image. Like a little sticky note informing you—and everyone else using the image—something significant about it, including what version it is or what type of software it contains. Consider it as a means of maintaining easily comprehensible and orderly Docker images.
Working on our Docker images, we had assumed that we are all by ourselves. That won't always be the case, though, particularly as our development team expands. The following post will help us to understand how best to tag and name our images for a team environment. Following these guidelines will help us to maintain our Docker images clear and understandable for every project participant.
There are two basic approaches for labeling and naming Docker images. When you build an image from a Dockerfile, you can either use the -t
option, or the docker tag
command. When you use the docker tag
command, you specify the source repository name you are going to use as the base and the target name and tag you'll be creating
docker tag <source_repository_name>:<tag> <target_repository_name>:tag
When you use the docker build
command, to name your image, you will use the Dockerfile as the source and then the -t
option to name and tag your images
docker build -t <target_repository_name>:tag Dockerfile
Occasionally you may find a repository name beginning with a hostname. This is only a means of informing Docker where the repository resides online; and it is absolutely optional. In future posts in this series we are going to create our own Docker Registry, and see how this works. If you're uploading your images to Docker Hub, though, you'll definitely need to add your Docker Hub username to the beginning of the repository name, like this:
docker built -t <dockerhub_user>/<target_repository_name>:tag Dockerfile
In the following example, we are going through the process of tagging Docker images.
Tagging Docker Images
In this example, we are going to work to use a new image, using the lightweight busybox
image to demonstrate the process of tagging and start to implement tags in our projects. BusyBox is used to combine mini versions of many common UNIX utilities into a single small executable.
Run the docker rmi
command to clear up the images you currently have on your system, so you don't get confused with a large number of images around:
docker rmi -f $(docker images -a -q)
On the command line, run the docker pull
command to download the latest busybox
container
docker pull busybox
Run the docker images
command
docker images
This will give us the information we need to start putting some tag commands together
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 65ad0d468eb1 13 months ago 4.26MB
Okay, let's give your image a name and tag it so it's easier to find and work with later. We can do this using either the image's unique ID or its repository name.
For now, we'll use the image ID. Keep in mind that your image ID will be different from the one I use in the example.
docker tag 65ad0d468eb1 mybusybox:v1
This command will create a new tag called v1
for your image in the mybusybox
repository. Now you can refer to your image using either its ID or the more human-readable name mybusybox:v1
.
Now that we've tagged our image, let's create a new repository using your name and a new version tag. Since we've already tagged the image as mybusybox:v1
, we can use that to create a new tag with the updated version.
docker tag mybusybox:v1 kalkost/busybox:v1
Run the docker images
command
docker images
You should see an output similar to the following
kalkost/busybox v1 65ad0d468eb1 13 months ago 4.26MB
busybox latest 65ad0d468eb1 13 months ago 4.26MB
mybusybox v1 65ad0d468eb1 13 months ago 4.26MB
Let's put together a Dockerfile to create a basic image, using the mybusybox
image we tagged earlier. We'll also include a tag for the new image we're creating, since Docker might try to use the latest
tag if we don't specify one, and that could cause errors if it doesn't exist.
echo "FROM mybusybox:v1" > Dockerfile
Run the docker build
command to create the image while naming and tagging it at the same time
docker build it built_image:v1.1.1 .
Note that when I tried the above command, the Dockerfile was created with a UTF-16 encoding causing the very user friendly ERROR: failed to solve: Internal: Internal: Internal: stream terminated by RST_STREAM with error code: INTERNAL_ERROR
error to appear. If you encounter this issue, then open the Dockerfile with any editor of your choosing and change the encoding to UTF-8
Now, run the docker images
command to see the four images available on your system
docker images
You should see something similar to the following
REPOSITORY TAG IMAGE ID CREATED SIZE
built-image v1.1.1 0534e936c658 13 months ago 4.26MB
busybox latest 65ad0d468eb1 13 months ago 4.26MB
mybusybox v1 65ad0d468eb1 13 months ago 4.26MB
kalkost/busybox v1 65ad0d468eb1 13 months ago 4.26MB
Tagging your Docker images with a meaningful version number that makes sense for your team or company is actually pretty simple, especially once you've done it a few times. The tips we've covered here will help you avoid the default latest
tag, which can cause problems down the line. In the next section, we'll dive into those potential issues and why using specific tags is a smart move.
Using the Latest Tag in Docker
As we've been working with tags, I've mentioned a few times to avoid using the default latest
tag. It might seem convenient, but it can actually cause a lot of headaches, especially when you're deploying images to production. Let's dive into why it's best to steer clear of latest
and use specific tags instead.
The first thing to understand is that the latest
tag is just a regular tag, like the v1
we used earlier. It doesn't automatically mean the newest version of your code. It actually refers to the most recent image you built without specifying a tag.
Here's the thing about using the latest
tag: it can really mess things up when you have a big team constantly deploying updates to different environments. It's like trying to keep track of a moving target – you never quite know which version you're actually dealing with. And if something goes wrong, good luck rolling back to a previous version without a clear history.
Remember, Docker doesn't automatically pull the most recent image when you use latest
. It just grabs the most recent one you've built or pulled without specifying a tag. This can lead to all sorts of confusion and unexpected behavior, especially in production.
Issues when Using Latest
Since you might still getting familiar with Docker and tagging, you might not have run into any problems using the latest
tag yet. But don't worry, this next exercise will give you a firsthand look at how latest
can throw a wrench in your development process. You'll understand why it's best to avoid it and start using specific tags instead.
We're going to build on the previous Dockerfile in this example and explore the potential pitfalls of relying on the latest
tag. Get ready to see the latest
tag in action (or inaction, as the case may be).
Let's update the that Dockerfile we created to include a simple version script.
FROM mybusybox:v1
RUN touch /version.sh && \
echo '#!/bin/sh' >> /version.sh && \
echo 'echo "Version: 1.0"' >> /version.sh && \
chmod +x /version.sh
ENTRYPOINT ["sh", "/version.sh"]
Build the image and name it whatever you want
docker build -t kalkost/test .
Run the image using the docker run
command
docker run kalkost/test
You should see the output of the version.sh
script
Version: 1.0
Use the docker tag
command to tag this image as v1
docker tag kalkost/test kalkost/test:version1
Change the value of the version of the Dockerfile in line 5
FROM mybusybox:v1
RUN touch /version.sh && \
echo '#!/bin/sh' >> /version.sh && \
echo 'echo "Version: 2.0"' >> /version.sh && \
chmod +x /version.sh
ENTRYPOINT ["sh", "/version.sh"]
Build the amended Dockerfile and tag it with v2
docker build -t kalkost/test:v2 .
Run the amended image using the docker run
command
docker run kalkost/test
Now you might think that you are going to see the version changed, but you will see the following
Version: 1.0
Hold on a second! This isn't the version we expected, right? It seems Docker pulled the most recent version of the image that was tagged with latest
instead of the one we just built with the 1.0
tag in step 3. This is a perfect example of how using the latest
tag can lead to unexpected results and confusion.
Now, run both images with latest
and v2
tags
docker run kalkost/test:latest
Version: 1.0
And
docker run kalkost/test:v2
Version: 2.0
You probably guessed it - to run the updated version of the code with the version script, we need to specify the 1.0
tag that we used when building the image. If you don't, Docker defaults to the most recent image with the latest
tag, which might not be what you want.
This example clearly shows how using the latest
tag can create confusion, especially in a team setting where multiple developers are pushing images to a shared repository. It's like playing a guessing game with which version is actually running in production.
Things can get even messier if you're using orchestration tools (like Kubernetes) and pulling the latest
image. You could end up with a mix of different versions running in your production environment, which is a recipe for unexpected behavior and potential bugs.
The key takeaway here is that using specific tags is crucial for maintaining control and predictability in your Docker workflow. It ensures that everyone is on the same page and helps avoid those frustrating latest
tag surprises.
Docker Image Tagging Policies
As development teams grow and projects become more complex, having a standard tagging policy for your Docker images becomes increasingly important. Without a clear and consistent approach to tagging, you risk running into the kind of confusion and problems we've explored in earlier sections.
Deciding on a tagging policy early in the game is a smart move to avoid these headaches. There's no one-size-fits-all answer, but the key is to choose a policy that works for your team and stick to it. This section will cover some different tagging strategies you can use, along with examples of how to implement them. Remember, the goal is to find a policy that everyone on your team agrees on and can easily follow.
Semantic versioning is a popular and reliable system that can also be used for tagging your Docker images. If you haven't heard of it before, it's a simple three-part number system (major
.minor
.patch
). For example, a version like 2.1.0
would indicate a major release of version 2
, a minor release of 1
, and no patches. It's a great way to keep track of your image versions, especially in automated build environments where it can be easily automated.
Another option is to use a hash value, such as the git commit hash for your code. This allows you to easily link the image tag back to the specific code changes in your repository, making it super easy for anyone to see what's been updated.
Using a date value is another simple option that can be automated. It gives you a clear timestamp for each image, which can be helpful for tracking and troubleshooting.
The key takeaway here is that automating your tagging policy is the best way to ensure consistency, clarity, and adherence across your team.
Automating Image Tagging
Alright, let's explore how we can automate tagging your Docker images. This will minimize manual intervention and streamline your workflow. We'll continue working with the familiar base-image
for this example.
Create a base-image
using the following Dockerfile
FROM alpine:latest
RUN apk upgrade --no-cache && apk update && apk add wget curl
ARG VERSION=0.0.0
# Copy the version.txt file to the image
COPY version.txt /version.txt
# Command to read and display the version
CMD ["cat", "/version.txt"]
Create a version.txt
file with the following content
1.0.0
Now, create the following build.sh
#!/bin/bash
# Read current version from version.txt
current_version=$(cat version.txt)
# Extract the major and minor versions, set patch to 0
major_version=$(echo $current_version | awk -F. '{print $1}')
minor_version=$(echo $current_version | awk -F. '{print $2}')
new_minor_version=$((minor_version + 1))
new_version="$major_version.$new_minor_version.0"
# Update version.txt with the new version
echo $new_version > version.txt
# Build the Docker image with the new version as a build argument
docker build -t base-image:$new_version --build-arg VERSION=$new_version .
Let's break down what the script does
#!/bin/bash
# Read current version from version.txt
current_version=$(cat version.txt)
This part reads the current version number from a file named version.txt
and stores it in the variable current_version
. In a more real-life setting you would probably use Git to update the versions. If you are using Git, change the above command with the following
# Get the current version from Git tags
current_version=$(git describe --tags --abbrev=0)
# Extract the major and minor versions, set patch to 0
major_version=$(echo $current_version | awk -F. '{print $1}')
minor_version=$(echo $current_version | awk -F. '{print $2}')
new_minor_version=$((minor_version + 1))
new_version="$major_version.$new_minor_version.0"
These lines use awk
to parse the current_version
string (which is assumed to be in MAJOR.MINOR.PATCH
format, e.g., 1.2.3
) and extract the MAJOR
and MINOR
version components into variables major_version
and minor_version
respectively. Then it increments the MINOR
version by 1 to get the new MINOR
version number.
Finally the script constructs the new_version
. new_version
is constructed by combining the major_version
, incremented minor_version
, and setting the PATCH
to 0
. This forms a new version number, e.g., if current_version
is 1.2.3
, then new_version
will become 1.3.0
.
# Update version.txt with the new version
echo $new_version > version.txt
This line updates the version.txt
file with the newly created new_version
. It overwrites the existing version number stored in version.txt
with the incremented version.
# Build the Docker image with the new version as a build argument
docker build -t base-image:$new_version --build-arg VERSION=$new_version .
Finally, this command builds a Docker image named base-image
with the tag $new_version
(e.g., base-image:1.3.0
). The --build-arg VERSION=$new_version
option passes the new_version
as a build argument to the Docker build process. The .
at the end specifies the current directory as the build context.
Make sure that you have permissions to run the script using the chmod
command
chmod +x build.sh
Now, if you run build.sh
you will see that the script calculates the new version of the image and builds the Docker image
./build.sh
And the output should be something similar to the following
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 120B done
#1 DONE 0.0s
#2 [internal] load .dockerignore
#2 transferring context: 2B done
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/alpine:latest
#3 ...
#4 [auth] library/alpine:pull token for registry-1.docker.io
#4 DONE 0.0s
#3 [internal] load metadata for docker.io/library/alpine:latest
#3 DONE 4.1s
#5 [1/2] FROM docker.io/library/alpine:latest@sha256:b89d9c93e9ed3597455c90a0b88a8bbb5cb7188438f70953fede212a0c4394e0
#5 DONE 0.0s
#6 [2/2] RUN apk upgrade --no-cache && apk update && apk add wget curl
#6 CACHED
#7 exporting to image
#7 exporting layers done
#7 writing image sha256:b5e88b0b4601183bb9425186cd95fee52e22be5f1ec8c58b95a50f42e78c2004 done
#7 naming to docker.io/library/base-image:1.1.0 done
#7 DONE 0.0s
View the image using the docker images
command
docker images
It should reflect the name and tags created as part of the build script
REPOSITORY TAG IMAGE ID CREATED SIZE
base-image 1.1.0 b5e88b0b4601 29 minutes ago 22MB
Conclusion
We've discussed the importance of image tags in Docker and provided guidelines for tagging and naming them, especially in a team environment. We saw two approaches for labeling and naming Docker images: using the -t
option or the docker tag
command.
We also discussed examples of tagging Docker images and highlighted the potential issues with using the latest
tag. We then saw the need for a clear tagging policy and suggested using semantic versioning, hash values, or date values for image tags.
Finally, we discussed the importance of automating the tagging policy to ensure consistency and clarity.
Top comments (0)