A Deep Dive on Docker Tags: What They Are and How to Use Them Effectively

Docker whale logo

Tags are a fundamental concept in Docker that often don‘t get enough attention. While simple on the surface, they have subtle but important implications for how you manage and deploy your images. Let‘s take a comprehensive look at what Docker tags are, how they work, best practices for using them, and some of the pitfalls to avoid.

The Anatomy of a Docker Tag

In the most basic sense, a Docker tag is a label you can apply to an image to give it a human-readable name. More precisely, a tag is an alias that points to a specific image ID. Image IDs are SHA256 hashes that look something like sha256:c54a2cc56cbb2f04003c1cd4507e118af7c0d340fe7e2720f70976c4b75237dc. Tags let you refer to that image using a friendlier name like myimage:v1.2.3.

The general format for specifying an image with a tag is:

username/imagename:tag

The username is your user or organization name on a registry like Docker Hub. If you omit this, Docker assumes you‘re referring to an official image.

The imagename is the name you give to your image. Combined with the username, this forms the image‘s repository name.

After the colon comes the tag, which typically indicates a specific version or variant of the image. A tag can be any string, but usually follows some convention like semantic versioning (more on this later).

Some examples of tagged image names:

mongo:4.2.0
python:3.7-slim-buster
nginx:stable-alpine

If you don‘t specify a tag, Docker assumes you mean the latest tag. But this default tag can be misleading, as we‘ll see.

Building Images with Tags

There are two main ways tags come into play when working with Docker images:

  1. When you build an image using docker build
  2. When you tag an existing image using docker tag

To tag an image as you build it, use the -t or --tag flag:

docker build -t myusername/myimage:v1.0 .

This builds the image described by the Dockerfile in the current directory and tags the resulting image as myusername/myimage:v1.0.

If you run docker images afterward, you‘ll see your new image in the list, with "myusername/myimage" under the REPOSITORY column and "v1.0" under the TAG column.

You can specify multiple tags for an image by repeating the -t flag:

docker build -t myusername/myimage:v1.0 -t myusername/myimage:latest .

This tags the image both as v1.0 and latest.

What if you want to tag an image that you‘ve already built? That‘s where docker tag comes in.

Tagging Existing Images

The docker tag command lets you create a new tag that points to an existing image:

docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

This creates a new tag TARGET_IMAGE that refers to the same image as SOURCE_IMAGE. Some examples:

docker tag myimage:v1.0 myimage:latest
docker tag redis:6.0.1 myrepo/redis:6.0.1
docker tag 7d9495d03763 myimage:v1.1

The first example tags the existing myimage:v1.0 image as myimage:latest as well. The second tags a local Redis image to be pushed to a repository called "myrepo". The third tags an image by its ID (useful if the image doesn‘t have a tag).

An important thing to understand is that docker tag doesn‘t create a new image, it just creates a new alias for an existing image. Both tags refer to the exact same underlying image.

The Trouble with Latest

you‘ve probably noticed that if you don‘t specify a tag when building or tagging an image, it gets the latest tag by default. This might make you think that an image tagged latest must be the most recent version of that image. But that‘s a dangerous assumption.

The latest tag is just Docker‘s default tag when you don‘t specify one. That‘s it. There‘s no guarantee that the image tagged latest is actually the newest version of that image. All latest really means is "the image that has the latest tag". And since tags can be moved around, that could point to an old version.

Consider the official Python image on Docker Hub:

Tag Digest
latest sha256:028a1cbc517dd371b1aaa52bc339f514d2d34de4e22fe2f3f349e39e5fc97f9c
3.9.0 sha256:d3542ad1eb158e0601aa1a14dc168f8d7e644f49b8e33ebc7d9c45c0c2aef5c6
3.8.5 sha256:028a1cbc517dd371b1aaa52bc339f514d2d34de4e22fe2f3f349e39e5fc97f9c

Notice how latest points to the same image as 3.8.5, even though 3.9.0 is a newer version. Simply put, you can‘t rely on latest meaning "newest".

This is why using latest in Dockerfiles can lead to unexpected behavior. Consider these two Dockerfile FROM statements:

FROM python
FROM python:3.8.5

The first one is equivalent to FROM python:latest. Every time you build that Dockerfile, you might be basing your image on a different Python version, without realizing it. In the second example, you know you‘re always getting Python 3.8.5, even if a newer version comes out.

Semantic Versioning with Tags

So if you can‘t count on latest, how should you tag your images? The most common approach is to use semantic versioning, or SemVer. With SemVer, a version number has three parts: MAJOR.MINOR.PATCH. You increment:

  • MAJOR when you make incompatible API changes
  • MINOR when you add functionality in a backwards compatible manner
  • PATCH when you make backwards compatible bug fixes

Docker image tags often follow this pattern, with additional tags for just the major and minor parts:

myimage:2
myimage:2.3
myimage:2.3.1

The myimage:2 tag points to the latest image with a major version of 2. The myimage:2.3 tag points to the latest 2.3.x version. And myimage:2.3.1 points to that exact patch version.

Many of the official images on Docker Hub use this convention:

redis:6
redis:6.0
redis:6.0.1
mysql:8
mysql:8.0
mysql:8.0.21

Tagging this way lets users choose how specific they want to be with versioning. Using a major version tag gives you the latest features but risks breaking changes. Using a full patch version gives you stability but you might miss out on bug fixes.

Tag Naming Best Practices

Beyond semantic versioning, there are some general best practices to follow when naming your tags:

  • Be specific. Tags like myimage:v1 or myimage:test don‘t convey much useful information. Use tags that clearly indicate the image‘s version or purpose.

  • Be consistent. Decide on a tagging scheme and stick to it across all your images. Common patterns are vX.Y.Z for version numbers, or X.Y-os-version for operating system variants.

  • Don‘t use latest as a fixed version. Reserve latest for tags that you intentionally move to newer versions. Better to use an explicit version as your default tag.

  • Avoid using floating tags like -alpine or -slim alone. Always pair them with a version tag so it‘s clear what version of the base image they‘re built from.

Remember that a tag doesn‘t have to be just a version number. Tags are flexible and can indicate other attributes of an image, like:

  • The Linux distribution it‘s based on: myimage:ubuntu-20.04, myimage:alpine-3.12
  • The CPU architecture it‘s built for: myimage:v1.2.3-amd64, myimage:v1.2.3-arm64v8
  • A specific build or configuration: myimage:v1.2.3-debug, myimage:v1.2.3-optimized

Tagging and Versioning in Practice

To see these tagging patterns in action, let‘s look at some of the official images on Docker Hub.

The Nginx image uses a combination of semantic versioning and distribution tags:

nginx:1.19
nginx:1.19.3
nginx:1.19.3-alpine
nginx:1.19.3-alpine-perl
nginx:stable
nginx:stable-alpine

The MySQL image does something similar, but without the distribution tags:

mysql:8
mysql:8.0
mysql:8.0.22
mysql:5
mysql:5.7
mysql:5.7.32

Notice how they provide tags for each level of granularity: major version only, major.minor, and major.minor.patch.

The Python image offers tags for different versions of Python on different Linux distributions:

python:3
python:3.9
python:3.9.0
python:3.9.0-buster
python:3.9.0-slim-buster
python:3.9.0-alpine
python:3.9.0-windowsservercore-ltsc2016

This lets users choose the specific Python version and operating system they need.

Advanced Tagging Techniques

Beyond the basics, there are some more advanced ways you can use Docker tags.

Multi-architecture images

With the advent of multi-platform images in Docker 19.03, you can use a single tag to refer to images built for different CPU architectures. For example, you could have:

myimage:v1.2.3-amd64
myimage:v1.2.3-arm64v8

Both tagged as myimage:v1.2.3. When you docker pull myimage:v1.2.3, Docker will automatically fetch the image that matches your host system‘s architecture.

Signing images

Docker Content Trust lets you sign your images to ensure their integrity and authenticity. When you enable content trust, Docker will only pull signed images, and will refuse to pull an image if its signature doesn‘t match the expected digest.

With content trust enabled, Docker uses a separate tag scheme for signed images, appending the signature digest to the tag:

myimage:v1.2.3
myimage:v1.2.3-5926d4c0b5cdb1525a0b38d31176e83e60d69b38d0737c4cc0a7347b2392a20a

This ensures that you‘re always pulling the specific image version that you signed.

Labels vs tags

In addition to tags, Docker supports adding metadata to images in the form of labels. While tags are used for naming and versioning, labels are better suited for adding arbitrary key-value data to an image, like the build date, VCS ref, or license.

You can add labels to an image in the Dockerfile:

LABEL version="1.2.3"
LABEL maintainer="Me <[email protected]>"
LABEL org.opencontainers.image.authors="Me <[email protected]>"

Or when building the image:

docker build -t myimage:v1.2.3 --label version=1.2.3 .

You can then filter images by label using docker images -f:

docker images -f label=version=1.2.3

Pruning Images

As you work with Docker, your system can accumulate a lot of images, many of which you might not need anymore. Docker provides the docker image prune command to clean up unused images.

By default, docker image prune will remove all dangling images – those that aren‘t tagged and aren‘t referenced by any container. You can remove all images that aren‘t being used by a container with the -a flag:

docker image prune -a

You can also filter the images to prune based on age, size, or label:

docker image prune -a --filter "until=24h"
docker image prune -a --filter "label!=keep"

Conclusion

We‘ve covered a lot of ground on Docker tags, from the basics of what they are and how to use them, to best practices and advanced techniques. To recap the key points:

  • A tag is a human-friendly alias for a specific image ID
  • You can assign tags when building an image with docker build -t and to an existing image with docker tag
  • If you don‘t specify a tag, Docker uses latest by default, but this doesn‘t mean "newest version"
  • Follow semantic versioning conventions for tagging versions, and be as specific as your use case requires
  • Avoid using latest or floating tags as fixed version references in production
  • Use multi-stage builds to minimize final image size while still using descriptive tags during build
  • Consider signing your images with Docker Content Trust to ensure integrity
  • Use labels for adding arbitrary metadata to complement tags
  • Periodically prune unused images to free up disk space

Tags are a key part of working with Docker, enabling you to manage image versions, ensure repeatability in builds and deployments, and collaborate effectively. By following tagging best practices and conventions, you can make your Docker workflow more predictable and productive.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *