How a Dockerfile Becomes an Image
Summary
- A Dockerfile is a declarative recipe;
docker buildturns it into an immutable OCI image made of stacked, content-addressed layers. - Insight: The image is not a snapshot of a running process. It is a filesystem plus metadata, assembled at build time and frozen.
- Key Concepts: build context, instructions, layers, image manifest, config blob, content-addressable digests, build cache.
- When to Use: Understand this model before optimizing build speed, shrinking images, or debugging why a cache keeps missing.
- Limitations/Trade-offs: Every instruction that changes the filesystem adds a layer; poor ordering wastes cache and bloats the final image.
- Related Topics: Layers and union filesystems, multi-stage builds, BuildKit cache mounts.
Foundations
An image is a portable, read-only bundle. It contains a stack of filesystem layers plus a JSON config that records the default command, environment, working directory, and user.
A container is a running instance of that image with a thin writable layer on top. The image is the template; the container is the process.
The Dockerfile describes how to build that image, one instruction at a time. Think of it as a reproducible script whose output is a filesystem, not a program that runs at deploy time.
Modern Docker Engine 29.x builds with BuildKit by default. BuildKit reads the whole Dockerfile, builds a dependency graph of the instructions, and executes only what the requested target needs.
Mechanics & Interactions
The build starts by collecting the build context - the directory you point docker build at. That directory is sent to the builder, which is why a bloated context slows every build.
docker build -t myapp:1.0 .BuildKit then evaluates instructions. Filesystem-modifying instructions like RUN, COPY, and ADD produce a new layer. Metadata instructions like ENV, WORKDIR, USER, CMD, and ENTRYPOINT change the image config without adding a filesystem layer.
Each layer is stored as a compressed tar and identified by the SHA-256 digest of its content. Identical content always yields the same digest, so layers are shared across images and pulls skip anything already present.
The build cache keys each step on its instruction plus its inputs. For RUN, the cache key is the command string. For COPY and ADD, the key includes a checksum of the copied files.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]If a step's key matches a prior build, BuildKit reuses the cached layer and every step after it is re-evaluated against that cached parent. The first changed step invalidates all downstream steps.
This is why dependency files are copied and installed before source code. Editing application code does not change package.json, so npm ci stays cached.
When the graph finishes, BuildKit writes an image manifest. The manifest lists the config blob digest and the ordered layer digests, and it is itself addressed by digest.
That manifest digest is the immutable identity of the image. A tag like myapp:1.0 is just a human-friendly pointer to a digest, and it can be moved; the digest cannot.
Advanced Considerations & Applications
Because layers are content-addressed, layer order controls both cache hit rate and pull size. Put rarely changing instructions early and frequently changing ones late.
Multi-stage builds exploit this model directly. You compile in a heavy builder stage, then COPY --from only the artifacts into a clean final stage, so build-time tools never ship.
BuildKit can export a docker-image, an OCI layout, or a plain filesystem, and it can push directly to a registry. The same graph feeds all of these outputs.
Reproducibility has limits. A step like RUN apt-get update pulls whatever package versions exist at build time, so two builds days apart can differ even with identical Dockerfiles. Pin versions and base digests when reproducibility matters.
The runtime never sees the Dockerfile. On a Kubernetes node, containerd pulls the image by manifest, unpacks its layers, and hands the rootfs to the container - the build instructions are long gone by then.
Common Misconceptions
"The image runs the Dockerfile at startup." No. The Dockerfile runs only at build time. At startup the runtime executes the image config's ENTRYPOINT/CMD.
"Each line is one layer." Only filesystem-changing instructions create layers. Metadata instructions modify config without a new layer.
"A tag uniquely identifies an image." Tags are mutable pointers. Only the digest (@sha256:...) is a stable identity.
"Squashing always helps." Squashing collapses layers into one, which can hurt cache reuse and cross-image sharing. It trades those savings for a marginally smaller manifest.
"Docker Engine runs my pods." Docker builds images, but containerd via the CRI runs containers on Kubernetes nodes. Dockershim was removed in Kubernetes 1.24.
FAQs
What is the difference between an image and a container? An image is a read-only, layered filesystem plus config. A container is a running instance with a writable top layer.
Why does my build ignore the cache after a small edit? Because the changed step invalidates every downstream step. Move volatile instructions like COPY . . as late as possible.
Do ENV and WORKDIR add layers? No. They update the image config only. They still participate in the cache key, so changing them invalidates later steps.
Is the Dockerfile stored inside the image? No. Only the resulting layers and config are stored. The Dockerfile stays in your source repository.
How do I see the layers of an image? Use docker history myapp:1.0 to list the layers and the instruction that created each one.
Are Docker-built images compatible with containerd? Yes. Both consume OCI images, so a Docker-built image runs unchanged under containerd on your cluster nodes.
Related
- Dockerfile Basics
- How Image Layers & Union Filesystems Work
- Multi-Stage Builds
- BuildKit & Cache Mounts
- Dockerfiles Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).