Where to start First steps track · 15 stages
Linux Labs

Container images — layers, tags, registries and digests

13 min Docker 29.1 · Ubuntu 24.04

An image is a read-only stack of layers with files and run settings; Docker creates containers from it.

Cheat sheet

10 commands Printable version
  • sudo docker load -i /opt/images/alpine.tar image from a file
  • sudo docker images images on this machine
  • sudo docker image ls --digests with digests
  • sudo docker history nginx:alpine layers and what made them
  • sudo docker image inspect -f '{{.Id}}' busybox full ID
  • sudo docker tag busybox:latest app:1.0 a second name, same image
  • sudo docker rmi app:1.0 drop a tag or the image
  • sudo docker image prune remove untagged images
  • sudo docker save busybox > busybox.tar image to a file
  • sudo docker pull nginx:alpine fetch from a registry (network)

Overview

An image is the template Docker creates containers from. It holds the container's files — the program, libraries, configs — and its run settings: command, environment variables, working directory, port. An image never changes. A container writes to its own layer on top of the image, so one image can back any number of containers.

The lab machine has no outside network. Images sit as files in /opt/images, and docker load brings them into Docker. Here student is not in the docker group, so every command goes through sudo.

text
student@ubuntu:~$ sudo docker load -i /opt/images/alpine.tar
Loaded image: alpine:latest
student@ubuntu:~$ sudo docker images
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
alpine:latest    d1e016453729       17.8MB         8.71MB
busybox:latest   27be924c62a0       9.26MB         4.68MB   U
nginx:alpine     1acb5b0222ee        144MB         70.4MB
Column What it shows
IMAGE image name: repository and tag
ID short ID — the first 12 characters of the hash
CONTENT SIZE compressed layers, as in the file or the registry
DISK USAGE space on disk including unpacked layers
EXTRA U — at least one container uses the image

After the table docker images prints a warning, This output is designed for human readability: the table layout may change between Docker versions. Scripts need --format — see the recipe below.

Image name and registry

A registry is a server that stores images: Docker Hub, GitLab Registry, a company registry. docker pull downloads an image from it, docker push uploads one. The full image name says where it lives:

text
registry.example.com:5000/team/app:1.4
└── registry ───────────┘└ path ─┘└ tag

With no registry in the name Docker goes to Docker Hub; with no tag it takes latest. nginx:alpine means docker.io/library/nginx:alpine. The full name shows up in the error when the image is missing: failed to resolve reference "docker.io/library/nginx:alpine".

Layers

An image is built from layers. A layer is an archive of file changes: what was added, changed or deleted relative to the layers below. Every dockerfile instruction that changes files produces a layer. docker history lists them, newest first:

text
student@ubuntu:~$ sudo docker history nginx:alpine
IMAGE          CREATED      CREATED BY                         SIZE
1acb5b0222ee   5 days ago   RUN /bin/sh -c set -x && apkArch…  52.4MB
<missing>      5 days ago   CMD ["nginx" "-g" "daemon off;"]   0B
<missing>      5 days ago   EXPOSE map[80/tcp:{}]              0B
<missing>      5 days ago   COPY docker-entrypoint.sh / # bu…  8.19kB
…
<missing>      4 weeks ago  ADD alpine-minirootfs-3.24.2-x86…  9.08MB

The bottom row is the Alpine layer: nginx:alpine is built on top of the Alpine image. Rows of size 0B (CMD, EXPOSE, ENV) change no files — they record run settings. <missing> in the IMAGE column means the step has no separate image on this machine; every pulled or loaded image looks like this.

Identical layers are stored once. Ten images based on Alpine keep a single copy of its layer on disk.

Container layer

Image layers are read-only. A container gets its own writable layer on top — the container layer. A file changed inside the container is copied up into that layer (copy-on-write); the image stays as it was.

The container layer lives as long as the container: docker stop and docker start keep it, docker rm deletes it with all its data. Data that must survive re-creating a container goes into a volume — see Volumes in the Docker article.

Tag

A tag is a movable name: the part after the colon. docker tag gives the same image a second name; both names share one ID:

text
student@ubuntu:~$ sudo docker tag busybox:latest app:1.0
student@ubuntu:~$ sudo docker images
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
app:1.0          27be924c62a0       9.26MB         4.68MB   U
busybox:latest   27be924c62a0       9.26MB         4.68MB   U

latest is just the default tag. It is used when no tag is given and says nothing about freshness: alpine:latest on this machine is whatever image was loaded into it.

A tag moves on rebuild. A second docker build -t app:1.1 hands the name to the new image, and the old one is left nameless, <none>:<none> — a dangling image. Plain docker images hides it; a filter finds it:

text
student@ubuntu:~$ sudo docker images --filter dangling=true \
    --format '{{.ID}} {{.Repository}}:{{.Tag}}'
a4eab66e5237 <none>:<none>

docker images -a lists such images as <untagged>.

docker rmi <name> removes a tag. If the image has other names, it stays: Untagged: busybox:latest. For the last name docker rmi deletes the image too — as long as no container needs it.

Digest and ID

A digest is the SHA-256 of the image manifest, the description of its layers and settings. Change a single byte in any layer and the digest changes. So nginx@sha256:1acb… always means the same content, while nginx:alpine may point to a different image tomorrow.

text
student@ubuntu:~$ sudo docker image ls --digests
REPOSITORY   TAG      DIGEST            IMAGE ID       …   SIZE
nginx        alpine   sha256:1acb5b0…   1acb5b0222ee   …   144MB
busybox      latest   sha256:27be924…   27be924c62a0   …   9.26MB

In Docker 29 images are stored by containerd, and a loaded image's ID is the same as its digest.

docker load and docker save

docker save writes an image with all its layers to a tar archive; docker load reads it back — on this machine or another one. That is how an image reaches a host with no registry access. The archive uses the OCI layout: index.json, manifest.json and a blobs/sha256 directory where every file is named after its own SHA-256.

Recipes

5

Which image a container runs

bash
sudo docker ps -a --format '{{.Names}} {{.Image}}'
sudo docker inspect -f '{{.Config.Image}} {{.Image}}' web

The first inspect field is the name the container was created with, the second is the image ID. If the name has since moved to a new image, the ID tells.

Check a digest by hand

text
student@ubuntu:~$ mkdir bb && sudo docker save busybox | tar -xf - -C bb
student@ubuntu:~$ ls bb
blobs  index.json  manifest.json  oci-layout
student@ubuntu:~$ sha256sum bb/blobs/sha256/27be924c62a0…
27be924c62a0fa666ec2763cd824eb5caa9a7b324a953496909ff11498a409ea  bb/…

index.json points to the manifest by digest, the manifest file is named with that digest, and sha256sum of it gives the same number. Registries and Docker use these hashes to check that a layer arrived intact.

Move an image to a host without network

bash
sudo docker save app:1.1 > app.tar     # where the image is
scp app.tar admin@server:/tmp/
sudo docker load -i /tmp/app.tar       # on the server

Image list for a script

bash
sudo docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}'
sudo docker image inspect -f '{{.Size}}' nginx:alpine

Free up space

bash
sudo docker system df                  # images, containers, volumes
sudo docker image prune                # untagged images only
sudo docker images --filter dangling=true   # what prune will remove

Troubleshooting

5

Unable to find image … locally

text
Unable to find image 'nosuch:1' locally
docker: Error response from daemon: failed to resolve reference
"docker.io/library/nosuch:1": … server misbehaving

No image with that name and tag is on the machine, so Docker went to the registry for it. docker run, docker build with such a FROM, and docker compose up all do this. Without network the request fails on lookup registry-1.docker.io. Compare the name and tag with docker images (busybox:latest and busybox:1.36 are different images) or load the image from a file with docker load -i.

conflict: unable to delete … must be forced

text
Error response from daemon: conflict: unable to delete busybox:latest
(must be forced) - container 5401987efe76 is using its referenced
image 27be924c62a0

A container needs the image, even a stopped one. Find it with docker ps -a --filter ancestor=busybox, remove it with docker rm, then the image.

Rebuilt the image, but the container runs the old one

A container is created from the image that held the name at docker run time. A new build under the same tag does not touch it. Re-create the container: docker rm -f web, then docker run with the same options. Compare IDs: docker inspect -f '{{.Image}}' web and docker image inspect -f '{{.Id}}' app:1.1.

<none> instead of a name

Dangling images remain after rebuilding under the same tag. They take space but do no harm. Clean up with docker image prune.

Permission denied on an archive from docker save

sudo docker save -o file.tar creates the file as root with mode 600, and student cannot read it. Redirect the output instead — sudo docker save busybox > busybox.tar: your shell creates the file, so it is yours.

Danger zone

2

docker image prune -a and docker rmi -f

prune -a removes dangling images and every other image that no container needs right now. Without network there is nothing to pull them back from: you have to find the file for docker load again. rmi -f lifts the guard against deleting an image a container needs.

latest in production configs

image: app:latest in compose or a start script means "whatever is under that name now". After docker pull or a new build the service quietly comes up on another version. Pin a version tag or a digest: nginx@sha256:1acb5b02….

Terms

5
image layer
A piece of an image — an archive of file changes made by one Dockerfile instruction. Layers are read-only and shared by images with the same base; docker history lists them. Also appears in: Dockerfile, Docker
container layer
The writable layer on top of the image layers: everything the container changes in its files lands here. It is deleted together with the container by docker rm. Also appears in: Docker
image tag
A movable image name after the colon: nginx:alpine, app:1.1. With no tag, latest is used. After a new build the same tag points to the new image. Also appears in: Dockerfile
image registry
A server that stores images and serves docker pull: Docker Hub, GitLab Registry, a company registry. The registry name is the first part of a full image name.
image digest
The SHA-256 of the image manifest, sha256:… — a fingerprint of the content. nginx@sha256:… always means the same layers, while a tag can move to another image.

See also

  • docker — running containers, volumes, networks, inspect.
  • dockerfile — building your own image and where layers come from.
  • docker-compose — several containers from one file.
  • cgroups-namespaces — how a container is isolated from the host.
  • tar — the docker save archive format.
  • disk-usage — where the disk space went.
Loading…