Skip to content
docktionary

Docker

Images & layers

An image is a read-only template built from stacked filesystem layers. Understanding layers is the difference between a 30-second build and a 5-minute one.

What's actually in an image

An image is metadata plus a list of layers. Each layer is a diff of the filesystem produced by one instruction in a Dockerfile — mostly RUN, COPY, and ADD. Layers are identified by a content hash, stored once, and shared between any images that happen to produce the same layer.

When you run a container from an image, Docker doesn't touch the image's layers at all. It adds one more layer on top — a thin writable layer unique to that container — using a union filesystem (overlay2, on modern Linux). Delete the container and that writable layer goes with it; the image underneath is untouched and ready to start another container.

Pulling and inspecting an image

terminal
# Pull an image from a registry (Docker Hub by default)
docker pull node:22-alpine

# List images you have locally, with their size
docker images

# See every layer in an image and how big each one is
docker history node:22-alpine

# Full metadata: entrypoint, exposed ports, env, layer digests
docker inspect node:22-alpine

Why layer order matters for build speed

Docker caches each layer and reuses it on the next build if the instruction and everything above it are unchanged. Put the things that change least often first, so a code change doesn't invalidate your dependency install.

Dockerfile
FROM node:22-alpine
WORKDIR /app

# Dependencies change far less often than source code, so install them
# in their own layer, before copying the rest of the app in.
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Now copy the source. Editing app code only invalidates the layers
# from here down -- the npm ci layer above stays cached.
COPY . .

CMD ["node", "server.js"]

Flip those two COPY steps and every single source change forces a full npm ci on every build.

Tags vs. digests

A tag like node:22-alpine is a mutable pointer — the maintainers can (and do) push a new image under the same tag when they patch it. For a reproducible build, pin to the immutable digest instead:

terminal
docker pull node:22-alpine
docker inspect --format='{{index .RepoDigests 0}}' node:22-alpine
# node@sha256:3b2c1f...

# Use that digest anywhere you need an exact, unchanging image
docker pull node@sha256:3b2c1f...

A digest only proves the content hasn't changed -- it doesn't say who published it. For that, see signing & verification.

Building and tagging

terminal
# Build the Dockerfile in the current directory, tag it twice
docker build -t docktionary/app:1.4.0 -t docktionary/app:latest .

# Push both tags to a registry
docker push docktionary/app:1.4.0
docker push docktionary/app:latest

# Remove dangling (untagged) images left over from rebuilds
docker image prune