DEV Community

Cover image for Docker Image Naming and Tagging
Kostas Kalafatis
Kostas Kalafatis

Posted on

Docker Image Naming and Tagging

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

On the command line, run the docker pull command to download the latest busybox container

docker pull busybox
Enter fullscreen mode Exit fullscreen mode

Run the docker images command

docker images
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Run the docker images command

docker images
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Build the image and name it whatever you want

docker build -t kalkost/test .
Enter fullscreen mode Exit fullscreen mode

Run the image using the docker run command

docker run kalkost/test
Enter fullscreen mode Exit fullscreen mode

You should see the output of the version.sh script

Version: 1.0
Enter fullscreen mode Exit fullscreen mode

Use the docker tag command to tag this image as v1

docker tag kalkost/test kalkost/test:version1
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Build the amended Dockerfile and tag it with v2

docker build -t kalkost/test:v2 .
Enter fullscreen mode Exit fullscreen mode

Run the amended image using the docker run command

docker run kalkost/test
Enter fullscreen mode Exit fullscreen mode

Now you might think that you are going to see the version changed, but you will see the following

Version: 1.0
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And

docker run kalkost/test:v2
Version: 2.0
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Create a version.txt file with the following content

1.0.0
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

Let's break down what the script does

#!/bin/bash

# Read current version from version.txt
current_version=$(cat version.txt)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
# 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

View the image using the docker images command

docker images
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)