Docker
Intermediate 16 min read Docker

Writing Dockerfiles

The Dockerfile is the source code of your image. Learn every major instruction, cache behavior, and the patterns that make builds fast, small and secure.

DockerfileCOPYRUNCMDENTRYPOINTmulti-stagecache
Prerequisites: Docker Images
Introduction

What is this?

A Dockerfile is a plain-text recipe executed top-to-bottom to assemble an image. Each instruction becomes a layer. The two most subtle distinctions are COPY vs ADD and CMD vs ENTRYPOINT — get those right and most Dockerfile mistakes disappear.

Motivation

Why do you need it?

  • It turns infrastructure into reviewable, versioned, auditable source code.
  • One file defines the entire runtime environment, eliminating drift between environments.
  • Follows the same engineering practices as application code: review, test, lint.
Think about it

Mental model

Imagine

A recipe card

Think of

A Dockerfile

The recipe lists ingredients (base image) and steps (instructions). Every cook produces the same dish, just as every build produces the same image.

Mechanics

How it works

  • FROM chooses the base image; every layer after it builds on it.
  • COPY/ADD inject files from the build context into the image.
  • RUN executes commands in a new layer on top of the current filesystem.
  • ENV/ARG set environment and build-time variables.
  • CMD sets the default command; ENTRYPOINT sets the immutable entrypoint; together they let you pass args.
  • Multi-stage builds copy artifacts between stages with COPY --from.

Instruction → layer mapping in a Dockerfile.

FROM base
RUN install
COPY app
ENV/EXPOSE
CMD/ENTRYPOINT
Reference

Key commands

docker build -t app .

Build from the Dockerfile in the context.

docker build --target builder -t app-build .

Build only a named stage (useful for debugging).

docker build --build-arg VERSION=1.0 -t app .

Pass a build argument into ARG instructions.

docker build --platform linux/amd64 -t app .

Build for a different architecture.

docker buildx build --push --platform linux/amd64,linux/arm64 -t user/app:1.0 .

Build and push multi-architecture images.

Hands-on

Examples

Multi-stage Go build

Multi-stage Go build
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server .
FROM scratch
COPY --from=builder /out/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/server"]
EXPOSE 8080

The builder stage compiles a static Go binary; the final stage copies only that binary and TLS certs into an empty scratch image — result can be under 10MB.

Real world

In production

  • Lint Dockerfiles in CI (hadolint) and scan images (Trivy) before promotion.
  • Distroless/scratch final stages shrink attack surface to just the app binary.
  • Multi-arch builds via buildx so one tag works on amd64 and arm64.
  • Signed images and a pinned base digest for supply-chain integrity.
Guidelines

Best practices

  • Prefer COPY over ADD; it's unambiguous.
  • Use ENTRYPOINT for the binary and CMD for default flags/args.
  • Consolidate RUN commands with && and clean up package managers in the same layer.
  • Copy dependency manifests before source to keep the expensive layer cached.
  • Set USER to a non-root account and drop unnecessary capabilities.
  • Add a HEALTHCHECK so orchestrators and load balancers know real readiness.
Watch out

Common mistakes

  • ADD a remote tarball/URL instead of curl in a RUN (unresolvable, unverifiable, uncacheable).
  • Putting credentials in ARG or ENV (they remain in image metadata and layers).
  • Using apt-get without apt-get clean in the same RUN, bloating the layer.
  • Running everything as root.
Root cause analysis

Common issues

Error
RUN apt-get update && apt-get install ... returns error 100
Root cause

Package lists unavailable or base image lacks the package manager index.

Fix

Chain apt-get update && apt-get install -y in one RUN; for alpine use apk add --no-cache.

Error
COPY --from=builder: no stage or image found
Root cause

Stage name mismatch or --target used on a build that needs that stage.

Fix

Verify the stage alias in FROM matches the --from reference.

Error
Multiple platforms feature is currently not supported
Root cause

Legacy builder used instead of buildx.

Fix

Enable BuildKit (DOCKER_BUILDKIT=1 or default in modern Docker) and use docker buildx.

When things break

Debugging tips

  • docker build --progress=plain to see every command's output.
  • docker build --no-cache to bypass stale cache.
  • Insert a temporary `RUN pwd && ls -la` or use `docker run --entrypoint sh IMAGE` to explore.
  • Use a linter: hadolint catches 95% of footguns automatically.
Interviews

Interview checkpoint

Practice

Hands-on lab

Multi-stage Dockerfile Mastery
40 min · intermediate
In progress
Objectives
  • Write a multi-stage Dockerfile
  • Cut final image size dramatically
  • Use build args and correct CMD/ENTRYPOINT
  • Lint the Dockerfile

Multi-stage Dockerfile Mastery

40 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which instruction is best for copying a directory into the image?

2

In a multi-stage build, how do you copy a file from an earlier stage?

3

Secrets passed as ENV in a Dockerfile are visible in the final image.

Quick reference

Cheat sheet

Core instructions
FROM base:tagBase image
COPY src dstCopy files from context
RUN commandExecute and record layer
ENV K=VRuntime env var
ARG K=VBuild-time variable
CMD ["cmd","arg"]Default args
ENTRYPOINT ["bin"]Fixed command
EXPOSE portDocument port (does not publish)
Go deeper

Official documentation