Write a Dockerfile: FROM, COPY, RUN, CMD and multi-stage builds
How a Dockerfile turns into an image layer by layer, what the main instructions do, how to order them for fast rebuilds, and how a multi-stage build keeps the final image small.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
A Dockerfile is a plain text recipe. docker build reads it top to bottom and runs each instruction in a temporary container, saving the result as a new image layer. Understanding that each line is a layer explains both why builds can be fast (unchanged layers are reused from cache) and why images can get bloated (files deleted in a later layer still exist in an earlier one).
A first Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
FROMpicks the base image. Always use a specific tag such as3.13-slim;latestchanges under you.WORKDIRsets the directory for the following instructions and creates it if needed.COPY src destcopies files from the build context (the directory you pass todocker build) into the image.ADDdoes the same plus URL downloads and tar extraction; preferCOPYunless you need those.RUNexecutes a command at build time and saves the result as a layer. Combine related commands with&&so they share one layer.EXPOSEdocuments the port the program listens on. It does not publish anything;-pat run time does.CMDis the default command when a container starts. The JSON array form avoids a shell wrapper and lets signals such as Ctrl+C reach the process.
Build and run it:
docker build -t myapp:1.0 .
docker run --rm -p 8000:8000 myapp:1.0
Order instructions for the cache
Docker reuses a cached layer only if the instruction and all layers before it are unchanged. For COPY, that means the copied files must be identical. The example above copies requirements.txt and installs dependencies before copying the rest of the source. Editing app.py then only invalidates the final COPY, and the slow pip install layer comes from cache. Put the things that change least at the top.
Keep files out with .dockerignore
Everything in the build context is sent to the daemon and is available to COPY . .. A .dockerignore file next to the Dockerfile excludes what should never end up in the image:
.git
.venv
__pycache__
node_modules
*.log
.env
CMD vs ENTRYPOINT
CMD is replaced entirely if you pass a command to docker run, for example docker run myapp:1.0 python -m pytest. ENTRYPOINT is always run and anything you pass becomes its arguments. The two combine: use ENTRYPOINT for the fixed program and CMD for default arguments.
ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]
Do not run as root
By default the process inside a container runs as root. Create a user in the image and switch to it after the installation steps that need root:
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
Multi-stage builds
Compiled languages need a compiler and build tools, but the finished program does not. A multi-stage build uses one FROM to build and a second, smaller FROM for the final image, copying only the result across. The build tools never appear in the image you ship.
FROM golang:1.27 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server .
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/server /server
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
The same pattern works for Node.js front ends (build with node, serve with nginx) and for Java (build with a JDK image, run on a JRE image). docker build --target build -t myapp:build . stops after the first stage when you want to debug it.
Checking the result
docker image ls myapp
docker history myapp:1.0
docker run --rm myapp:1.0 id
history shows each layer with its size, which is the quickest way to spot an instruction that made the image unexpectedly large.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0055 Docker basics: images, containers and the commands you need An image is a read-only package of an app and its dependencies, and a container is a running instance of it. Learn… Beginner · Explains an idea · Linux, macOS, Windows Article Docker basics 15 Oct 2026
- 0056 Docker Compose: run a small web app with a database Docker Compose describes several containers, their ports, volumes and environment in one compose.yaml file, then starts them all with docker compose up. Intermediate · Step by step · Linux, macOS, Windows Article Docker basics · Docker Compose 15 Oct 2026
- 0250 Containerize a Node.js app and run it with PostgreSQL using Docker Compose Package a small Node.js web app in a multi-stage Docker image, then run it next to PostgreSQL with Docker Compose, named volumes,… Intermediate · Guided project · Linux, macOS, Windows Article Docker basics · Docker Compose · PostgreSQL 15 Oct 2026
- 0268 What is a container? A container is a lightweight box that packs a program with everything it needs so it runs the same on any computer. Beginner · Explains an idea · Any system Article Docker basics 15 Oct 2026
- 0327 Docker images, containers and volumes explained An image is a read-only template, a container is a running copy of it with a thin writable layer, and a volume… Beginner · Explains an idea · Linux, macOS, Windows Article Docker basics 15 Oct 2026
- 0328 Install Docker Engine on Ubuntu and Debian Set up Docker's official apt repository, install Docker Engine with the Compose and Buildx plugins, run hello-world and let your user run… Beginner · Step by step · Linux Article Docker basics · Packages & updates 15 Oct 2026
Repos in the wild
- Watchtower (maintained fork): automatic container image updates nicholas-fedor/watchtower Watchtower watches your running containers, pulls newer versions of their images and restarts them with the same settings. This fork continues the project after the original containrrr repository was archived. GitHub
- Uptime Kuma: self-hosted uptime monitoring louislam/uptime-kuma Uptime Kuma is a self-hosted monitoring tool with a web UI that checks websites, TCP ports, DNS, Docker containers and more, draws uptime history and sends alerts. One container is all it needs. GitHub
- Trivy: scan container images, file systems and infrastructure code for known problems aquasecurity/trivy Trivy is an open source scanner from Aqua Security that finds known vulnerabilities, misconfigurations and exposed secrets in container images, project directories, Git repositories and Kubernetes clusters. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +3,932 chars