Dockerfile Multi-stage Reading
AS builder, COPY --from, CMD vs ENTRYPOINT, layer caching, .dockerignore — reading multi-stage Dockerfiles in plain English
Multi-stage Dockerfile vocabulary
- FROM image AS name — names a stage; later stages copy from it with
COPY --from=name - COPY --from=builder — copies files from another stage's filesystem, not the host
- ENTRYPOINT = fixed executable; CMD = overridable default arguments (appended to ENTRYPOINT)
- Combine RUN apt-get update && install — prevents stale cache; clean up in the same layer
- .dockerignore — excludes files from build context; prevents secret leaks and reduces build size
Question 0 of 5
Read this multi-stage Dockerfile. Identify the builder stage and the final stage:FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
- AS builder — gives the stage a name so later stages can reference it with
--from=builder - Builder stage — has all build tools (compilers, dev dependencies); this stage is thrown away after the build
- Final stage — starts fresh from a base image; copies only what's needed to run the app
- Why smaller? — the final image doesn't contain the TypeScript compiler, build tools, or source files — only the compiled output
What does COPY --from=builder do in a multi-stage Dockerfile?FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o /app/server .
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
- --from=builder — specifies the source stage by its
ASname; can also use a stage index (--from=0) - scratch — a special empty base image; using it means the final image contains only what you COPY into it; no shell, no OS utilities
- Go binary — statically linked Go binaries are self-contained; they don't need a Go runtime or libc, making
scratchideal - ENTRYPOINT ["/server"] — the binary is run directly as PID 1
What is the difference between CMD and ENTRYPOINT in a Dockerfile?# Option A
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# Option B
CMD ["nginx", "-g", "daemon off;"]
- CMD ["arg1", "arg2"] — provides default command/arguments; completely replaced if you pass arguments to
docker run - ENTRYPOINT ["exe"] — the executable is always run; arguments from CMD (or
docker run) are appended - Together —
ENTRYPOINT ["nginx"] + CMD ["-g", "daemon off;"]runsnginx -g "daemon off;"by default, butdocker run image -c /etc/nginx.confrunsnginx -c /etc/nginx.conf - exec form —
["exe", "arg"]runs without a shell (signals work correctly); shell formCMD exe argwraps in/bin/sh -c
Why should you combine apt-get update and apt-get install in a single RUN instruction?# Problematic
RUN apt-get update
RUN apt-get install -y curl git
# Correct
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
- Image layer — each
RUN,COPY,ADDinstruction creates a new read-only layer; Docker caches layers by instruction hash - Cache staleness — if
RUN apt-get updateis cached from a previous build and packages have updated,RUN apt-get installin the next layer will install the wrong (outdated) versions - Combining — forces update + install to always execute together; no stale cache risk
- rm -rf /var/lib/apt/lists/* — removes the downloaded package index from the layer, reducing image size (often 30-50 MB)
What does a .dockerignore file do?# .dockerignore
node_modules/
.git/
*.log
.env
.env.*
dist/
coverage/
.DS_Store
- Build context — when you run
docker build ., Docker sends the entire current directory to the daemon; this can be slow and large - .dockerignore — works like .gitignore but for Docker; excluded files are never sent to the daemon, even if a
COPY . .instruction exists - Security — prevents
.envfiles (with secrets) and.git/(with history) from being accidentally baked into the image - Performance — excluding
node_modules/(can be GB) drastically reduces build context size and speeds up builds
.env*, .git/, and node_modules/ to .dockerignore.