DevOps Peach

Docker & Kubernetes · Building Docker Images with Dockerfile · Lesson 1 of 4

Dockerfile and image layers

A Dockerfile is the recipe for an image; every instruction that changes the filesystem adds a layer on top of the base image.

Practice interactively →

Dockerfile

A Dockerfile is a text file of INSTRUCTION arguments lines, with # for comments. Docker runs the instructions top to bottom:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Layers

flowchart BT
  B["node:20-alpine (base layers)"] --> L1["WORKDIR /app"]
  L1 --> L2["COPY package*.json"]
  L2 --> L3["RUN npm ci"]
  L3 --> L4["COPY . ."]
  L4 --> M["EXPOSE, CMD: metadata only"]

RUN, COPY and ADD create layers that hold filesystem changes; ENV, EXPOSE, CMD… only record metadata (0B in docker history). Layers are read-only and shared between images, so docker pull only downloads the layers you are missing. Every unneeded file in a layer makes the image bigger.

Build context

docker build -t app:1.0 .

The . is the build context: the directory Docker sends to the builder. COPY can only take files from the context. Use .dockerignore to keep node_modules, .git… out of it.

Command Meaning
docker build -t app:1.0 . Build an image from ./Dockerfile
docker history app:1.0 Show the layers and the instructions that created them
docker tag app:1.0 registry.example.com/team/app:1.0 Add a name for pushing to a registry
docker push registry.example.com/team/app:1.0 Upload the image to the registry

Choosing a base image

Base Size Notes
debian, ubuntu Large Familiar tools, good for debugging
*-slim variants Medium Same distro with fewer packages
alpine Very small Uses musl instead of glibc; some binaries and Python wheels behave differently
Distroless Small Only the runtime, no shell or package manager
scratch Empty For static binaries such as Go programs

Smaller bases mean faster pulls and fewer packages to patch, but a base without a shell is harder to debug. Pick by what the app needs.

Keeping layers small

Files deleted in a later layer still exist in the earlier one, so the image does not shrink. Clean up in the same RUN that created the files:

RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*

--no-install-recommends skips optional packages. The same applies to package manager caches in npm, pip (--no-cache-dir), and others.

Inspecting an image

Tagging strategy

Avoid relying on latest: it is just a default tag, not "the newest", and it makes it unclear what is running. Common schemes are semantic versions (app:1.4.2, plus moving tags app:1.4 and app:1) and the Git commit SHA (app:3f9c2ab), which links an image to exact source. Production deployments can pin the digest for full reproducibility.

Registries in practice

Log in with docker login registry.example.com; credentials are stored in ~/.docker/config.json, ideally through a credential helper rather than in plain text. Cloud registries such as ECR, ACR, and Artifact Registry integrate with their platform's identity, so build agents do not need long-lived passwords.

Practice interactively →