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"]
- A Dockerfile must start with
FROM(only parser directives, comments andARGmay come before it). - Instructions are case-insensitive; UPPERCASE is a convention for readability.
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
docker image inspect app:1.0shows environment, entrypoint, exposed ports, and layer digests.docker history --no-trunc app:1.0shows the full command behind each layer, including anything accidentally baked in.- The open-source tool dive browses each layer's files and highlights wasted space.
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.