Dockerfile

On this page 7

A Dockerfile is a plain text file containing ordered instructions that define how a container image is built: which base image to start from, which files to copy, which commands to run and how the resulting container should start. In containers and cloud-native delivery, the Dockerfile is the reproducible recipe behind an image, and it is usually stored in version control next to the application code.

The build engine, today usually BuildKit, reads the Dockerfile and produces an image made of stacked file system layers plus metadata. The same format is accepted by other OCI-compatible builders such as Buildah and Kaniko, so a Dockerfile is not tied to a single tool.

How a Dockerfile builds an image

The builder processes instructions from top to bottom. Instructions that change the file system, mainly RUN, COPY and ADD, each create a new layer that records only the differences from the layer below. Other instructions, such as ENV, EXPOSE and CMD, change only the image configuration.

Every layer is identified by a content hash and cached. On a rebuild, the builder reuses a cached layer as long as the instruction and its inputs are unchanged; the first changed instruction invalidates the cache for itself and every instruction after it. This rule is the basis of almost all Dockerfile optimization.

The build context is the set of files sent to the builder, usually the project directory. A .dockerignore file excludes items such as .git, local dependencies and secrets, which keeps the context small and prevents accidental inclusion of sensitive files in the image.

Core Dockerfile instructions

A small set of instructions covers most real Dockerfiles. Knowing exactly what each one does avoids common confusion, for example between CMD and ENTRYPOINT or between COPY and ADD.

InstructionPurposeCreates a layer
FROMSets the base image and starts a build stageStarts from base layers
RUNExecutes a command during the buildYes
COPYCopies files from the build context or another stageYes
ADDLike COPY, also extracts local archives and fetches URLsYes
WORKDIRSets the working directoryMetadata, creates the directory if needed
ENV / ARGRuntime variables / build-time variablesMetadata only
USERSets the user for later instructions and runtimeMetadata only
CMD / ENTRYPOINTDefault command / fixed executable at startMetadata only
Frequently used Dockerfile instructions

The exec form of CMD and ENTRYPOINT, written as a JSON array, runs the program directly as PID 1 so it receives stop signals. The shell form wraps the command in /bin/sh -c, which can prevent graceful shutdown because the shell does not forward signals.

Multi-stage builds and image size

A multi-stage build uses several FROM instructions in one Dockerfile. Early stages contain compilers, package managers and test tools; the final stage copies only the built artifacts into a minimal runtime base. This separates build-time dependencies from what ships to production.

The choice of final base image has a large effect on size and attack surface. The table compares common options; sizes are approximate and change between releases.

Base image typeApproximate sizeTrade-off
Full distribution (Debian, Ubuntu)Roughly 70-130 MBFamiliar tools, larger attack surface
Slim variantsRoughly 30-80 MBFewer packages, still glibc-based
AlpineAbout 5-10 MBVery small, uses musl libc, compatibility caveats
DistrolessA few MB to tens of MBNo shell or package manager, harder debugging
scratchEmptyOnly for static binaries
Common base image choices

A Dockerfile is one artifact in a chain that ends with running containers. Confusing the links in that chain leads to misplaced configuration, such as putting runtime secrets into the build.

TermWhat it isRelation to the Dockerfile
DockerfileBuild recipe in text formSource of truth for the image
Container imageImmutable layers plus configurationThe output of building a Dockerfile
Container registryStorage and distribution service for imagesWhere built images are pushed
ContainerRunning instance of an imageStarts with the Dockerfile’s CMD and ENTRYPOINT
Docker ComposeFile describing multi-container applicationsReferences images or Dockerfiles to build
Dockerfile and neighboring container terms

Writing an efficient and secure Dockerfile

Good Dockerfiles share a few habits. The most important is ordering instructions by change frequency: copy dependency manifests and install dependencies first, then copy the application source, so routine code changes do not invalidate the dependency layers.

MistakeConsequenceFix
Copying all source before installing dependenciesSlow rebuilds after every code changeCopy manifests first, then install, then copy source
Using the latest tag for the baseNon-reproducible buildsPin a version tag or digest
Secrets in ENV, ARG or copied filesCredentials stored in image layersUse build secret mounts and runtime injection
Running as rootHigher impact if the container is compromisedAdd a USER instruction with an unprivileged account
Package caches left in layersLarger imagesClean caches in the same RUN instruction
Dockerfile mistakes and their fixes

Deleting a file in a later instruction does not remove it from earlier layers; it only hides it. A secret copied in one step and deleted in the next remains extractable from the image, which is why sensitive data must never enter a regular layer.

Explained without jargon

A Dockerfile is a recipe for packaging an application. It lists, step by step, which starting ingredients to use, what to add and how to prepare everything, so that anyone following it gets exactly the same result.

The finished dish is the container image, which can be shipped and served anywhere. Because the recipe is written down, the package can be rebuilt identically at any time, on any machine, by any team member.

A typical use case

A development team ships a Node.js web service. Its original Dockerfile started from a full distribution image, copied the entire project first and ran the install step afterward, producing an image of about 1 GB that took several minutes to rebuild after every code change.

The team rewrites it as a two-stage build: the first stage installs dependencies and compiles the code, and the final stage copies only production files onto a slim runtime image, runs as an unprivileged user and uses the exec form of CMD. The image shrinks to roughly 150 MB, rebuilds after code changes take seconds thanks to layer caching, and vulnerability scans report far fewer findings.