Docker Images & Tags
Every container starts from an image, so choosing the right image is one of the most important decisions you make with Docker. A good choice gives you a small, safe, predictable container. A careless choice, such as always using latest, can break your app on a random Tuesday when a new version appears.
In this lesson you will learn to read image names, pick tags, pull and list images, look inside them and clean them up.
What is inside an image?
An image is a read-only bundle of files plus some settings. The files are a small Linux user space (for example Alpine or Debian), a language runtime (Node.js, Python, PHP), libraries and your app. The settings include the default command, environment variables, the working directory and which user to run as.
An image is stored as a stack of layers. Each layer holds the file changes made by one build step. Images that share layers store them only once on disk, so ten images based on node:26-alpine do not take ten times the space. You will study layers closely in Layers & the Build Cache.
Reading an image name
A full image reference has up to four parts:
ghcr.io/priya-sharma/medical-shop:2.1.0
└──┬──┘ └────┬─────┘ └─────┬────┘ └─┬─┘
registry namespace repository tag- Registry: the server that stores the image. If you leave it out, Docker uses Docker Hub (
docker.io). - Namespace: usually a user or organisation name. Official images on Docker Hub, such as
nginxormysql, live in a special namespace calledlibrary, which you never need to type. - Repository: the image's name.
- Tag: a version label. If you leave it out, Docker uses the tag
latest.
So nginx really means docker.io/library/nginx:latest, and mysql:8.4 means docker.io/library/mysql:8.4.
Tags and variants
Official images publish many tags for the same software. For Node.js, you will see tags like these:
| Tag | Meaning |
|---|---|
node:26 | Latest Node.js 26.x on a full Debian base with build tools (large) |
node:26-slim | Node.js 26.x on a trimmed Debian base (medium) |
node:26-alpine | Node.js 26.x on Alpine Linux (small) |
node:26.1.0-alpine | Exactly version 26.1.0 on Alpine |
node:latest | Whatever the newest Node.js release is today |
Alpine images are tiny because Alpine uses the small musl C library and BusyBox tools. A few packages with compiled parts behave differently on musl, so if something fails on Alpine, try the -slim Debian variant. Both are good choices; the full images are mainly useful when you need compilers during a build.
A tag can move: node:26-alpine points to a newer image every time Node.js 26 gets a patch. If you need an image that can never change, use its digest, a SHA-256 fingerprint of the image's content:
FROM node:26-alpine@sha256:9a0b4c1d2e3f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0bDocker checks the content against the digest, so nobody can swap the image behind your back. Teams often let a bot such as Dependabot or Renovate update pinned digests through pull requests.
Pulling and listing images
docker pull downloads an image without running it. docker run pulls automatically, but pulling first is useful on servers, so the download does not happen while your app is down.
docker pull node:26-alpine26-alpine: Pulling from library/node 2d35ebdb57d9: Pull complete 6e5a7b8c9d01: Pull complete a3f1b2c4d5e6: Pull complete 7c8d9e0f1a2b: Pull complete Digest: sha256:9a0b4c1d2e3f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b Status: Downloaded newer image for node:26-alpine docker.io/library/node:26-alpine
Each Pull complete line is one layer. The last line shows the full name that Docker resolved. Now list the images on your computer:
docker image lsIMAGE ID DISK USAGE CONTENT SIZE EXTRA alpine:3.22 4bcff63911fc 12.8MB 3.99MB U hello-world:latest 56433a6be3fd 20.3kB 3.96kB U mysql:8.4 1f0a3c9d5b6e 1.03GB 238MB nginx:alpine 61e01287e546 75.4MB 21.9MB U node:26-alpine 9a0b4c1d2e3f 218MB 56.2MB ubuntu:26.04 4f6c5a0b6c1e 113MB 30.6MB U
This is the layout used since Docker 29. CONTENT SIZE is the compressed size you download. DISK USAGE is the space the image takes once unpacked. A U under EXTRA means at least one container (running or stopped) uses the image. The short form docker images does the same thing.
Inspecting an image
docker image inspect prints everything Docker knows about an image as JSON. The --format option picks out single fields using Go template syntax, which saves you scrolling:
docker image inspect --format '{{.Os}}/{{.Architecture}}' node:26-alpine
docker image inspect --format '{{json .Config.Cmd}}' node:26-alpine
docker image inspect --format '{{json .Config.ExposedPorts}}' nginx:alpinelinux/amd64
["node"]
{"80/tcp":{}}The first answer shows the image's platform. The second shows the default command, which runs when you start the container without giving your own command. The third shows that the Nginx image documents port 80 as the port its server listens on.
Platforms: amd64 and arm64
Images contain machine code for one CPU family. Most servers and Windows laptops use amd64 (also called x86-64). Apple silicon Macs, Raspberry Pi boards and many cheaper cloud servers use arm64. Official images are multi-platform: one tag holds a version for each CPU, and Docker picks the one that matches your machine automatically.
You can ask for a different platform with --platform. This is useful when your Mac is arm64 but your server is amd64:
docker pull --platform linux/amd64 nginx:alpine
docker run --rm --platform linux/arm64 alpine:3.22 uname -maarch64
Running a foreign platform works through emulation (Docker Desktop includes it; on a Linux server you install QEMU support first), which is slow. It is fine for a quick test, not for daily use. When you build your own images for a different server, you can build multi-platform images with docker buildx build --platform linux/amd64,linux/arm64.
Removing images
Images take disk space, and old ones pile up after many builds and pulls.
docker image rm ubuntu:26.04 # remove one image (same as docker rmi)
docker image prune # remove dangling images (no tag left)
docker image prune -a # remove every image no container uses
docker system df # see how much space Docker usesDocker refuses to remove an image that a container still uses, even a stopped one. Remove the container first, or add -f if you are sure. Be careful with docker image prune -a on a server: the next deploy will have to download everything again.
Common mistakes
- Using
latestin production. Your build silently changes when a new release appears. Pin a version. - Choosing a huge image without thinking.
node:26is several times bigger thannode:26-alpine. Bigger images download slower and contain more software that may need security fixes. - "exec format error" when a container starts. The image was built for a different CPU platform, for example an arm64 image built on a Mac and run on an amd64 server. Build for the right platform with
--platform. - Trusting random images. Prefer Docker Official Images and images from verified publishers. An unknown image can contain anything.
Key points
- An image reference is
registry/namespace/repository:tag; Docker Hub and thelatesttag are the defaults. - Pin a version tag such as
node:26-alpine, or a digest for a reference that can never change. - Alpine variants are smallest;
-slimDebian variants are a safe middle ground. docker image lslists images (Docker 29 shows DISK USAGE and CONTENT SIZE);docker image inspect --formatreads single fields.- Multi-platform images hold builds for amd64 and arm64;
--platformchooses one explicitly. docker image rm,docker image pruneanddocker system dfkeep disk use under control.
Exercise
Pull python:3.15-slim and python:3.15-alpine. Compare their sizes with docker image ls. Then use docker image inspect --format to print the default command of each image. Finally, remove the larger one.
Show solution
docker pull python:3.15-slim
docker pull python:3.15-alpine
docker image ls python
docker image inspect --format '{{json .Config.Cmd}}' python:3.15-slim
docker image inspect --format '{{json .Config.Cmd}}' python:3.15-alpine
docker image rm python:3.15-slimAdding a repository name after docker image ls shows only that repository's tags. Both images start the Python interpreter by default (["python3"]), and the Alpine one is noticeably smaller, so the slim one is the larger image to remove.
Quick Quiz
1. What full reference does Docker use when you type nginx?
Docker Hub is the default registry, library holds official images, and latest is the default tag.
2. Why should you avoid the latest tag for deployments?
Pinning a version makes builds repeatable and updates deliberate.
3. What does a digest such as @sha256:... guarantee?
A digest is a fingerprint of the content, so it cannot point to anything else.
4. A container fails with "exec format error" on your amd64 server. What is a likely cause?
Machine code for one CPU family cannot run directly on another.
Learn Docker & DevOps with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
