Dockerfile Cheatsheet - Docker Image Build Reference

A Dockerfile authority for developers who want their images small, fast to finalize, and without leaked secrets. Beyond listing instructions, it explains why layer ordering and build cache matter, how multi-stage builds keep runtime images lean, and how ADD vs COPY, ENV vs ARG, USER and HEALTHCHECK change security and behavior. By the end you can write a Dockerfile that builds quickly and produces a minimal, production-safe image.

Containers & Orchestration·38 commands·Last updated 2026-07-21
dockerdockerfileContainersImages

Typical Use Case

For developers who write Dockerfiles and care about image size. There are only two techniques that matter much, and both are powerful: multi-stage builds (install dependencies and toolchain at build time, ship only artefacts at run time, which can take an image from gigabytes to megabytes) and layer caching (copy the dependency manifest first so code edits do not bust the install layer). This sheet is organised into seven groups with complete examples you can adapt directly.

Base Instructions 7

FROM node:18-alpine
Set the base image; alpine variants are smaller
FROM scratch
Empty base image for minimal static binaries
WORKDIR /app
Set working directory, created automatically if missing
COPY package*.json ./
Copy files into the container, supports globs
RUN npm ci
Execute a command to build an image layer
EXPOSE 3000
Document a container port (no actual mapping)
CMD ["node", "app.js"]
Default start command, exec form is recommended

Env & Args 7

ENV NODE_ENV=production
Set an env var, also available at runtime
ENV PATH="/app/bin:$PATH"
Append to PATH to make custom commands global
ARG VERSION=1.0
Build-time argument, not present at runtime
ARG NPM_TOKEN
No default; pass via --build-arg
LABEL maintainer="email@example.com"
Add image metadata
USER appuser
Switch the runtime user to lower container privileges
HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
Health check; marks unhealthy after consecutive failures

File Operations 5

COPY src/ ./src/
Copy a directory into the container, target auto-created
COPY --chown=node:node . .
Set file owner on copy to avoid root ownership
COPY --chmod=755 entrypoint.sh /entrypoint.sh
Set exec permission directly on copy (BuildKit)
ADD https://example.com/file.tar.gz /tmp/
ADD can download URLs into the image
ADD file.tar.gz /app/
Auto-extract local tar; prefer COPY for plain files

Multi-stage Build 5

FROM node:18 AS builder
Name a build stage for later reference
COPY --from=builder /app/dist ./dist
Copy artifacts from a build stage to the final image
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/
Copy files from an external image without pulling first
FROM golang:1.21 AS builder
Compile stage of a Go multi-stage build
FROM alpine:3.18
Final image uses a smaller base, down to a few MB

Entrypoint 5

CMD ["node", "app.js"]
Default command, overridable by docker run args
ENTRYPOINT ["node"]
Fixed entrypoint, not overridden by run args
ENTRYPOINT ["node"] CMD ["app.js"]
Combine: ENTRYPOINT sets program, CMD sets default args
SHELL ["/bin/bash", "-c"]
Change default shell for RUN/CMD, common in Windows containers
ENTRYPOINT ["./docker-entrypoint.sh"]
Custom entry script for init before the main process

Build Optimization 6

COPY package*.json ./
Copy deps first then npm ci to leverage the cache layer
RUN npm ci && npm cache clean --force
Merge RUN to reduce layers and clean cache
RUN apk add --no-cache curl
Alpine installs without caching indexes, smaller image
.dockerignore
Exclude node_modules, .git and other unneeded build files
RUN --mount=type=cache,target=/root/.npm npm ci
BuildKit cache mount reuses npm packages across builds
FROM node:18-alpine
alpine base is ~5x smaller than the full image

Full Examples 3

FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist CMD ["node", "dist/index.js"]
Node multi-stage: build deps and compile, runtime keeps only artifacts and deps
FROM python:3.11 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt FROM python:3.11-slim WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . CMD ["python", "main.py"]
Python multi-stage: deps installed to --user, runtime copies only that dir
FROM golang:1.21 AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /app/server . FROM alpine:3.18 COPY --from=builder /app/server /app/server CMD ["/app/server"]
Go multi-stage build: static binary keeps the image under 10 MB

Parameter matrix

ParameterEffectExample
FROM node:18-alpineSpecifies the base image; alpine is about 1/5 the size of the full variant.FROM node:18-alpine
WORKDIR /appSets the working directory, creating it automatically if missing.WORKDIR /app
CMD ["node","app.js"]Default startup command; the exec form receives SIGTERM correctly.CMD ["node", "app.js"]
ENTRYPOINT ["node"]Fixes the entry point so run arguments cannot override it.ENTRYPOINT ["./docker-entrypoint.sh"]
ENVSets environment variables, available both at build time and runtime.ENV NODE_ENV=production
ARGA parameter available only at build time.ARG VERSION=1.0
COPY --chownSets the owner when copying, avoiding root-owned artifacts.COPY --chown=node:node . .
ADD vs COPYADD can download URLs and auto-extract; use COPY for ordinary files.ADD file.tar.gz /app/
multi-stage AS builderNames a build stage for later reference.FROM node:18 AS builder
COPY --fromCopies artifacts from a build stage or an external image.COPY --from=builder /app/dist ./dist
USERSwitches the runtime user to lower container privileges.USER appuser
HEALTHCHECKHealth check; consecutive failures mark the container unhealthy.HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
.dockerignoreExcludes files such as node_modules and .git that need not enter the image..dockerignore
--mount=type=cacheBuildKit cache mount, reusing package caches across builds.RUN --mount=type=cache,target=/root/.npm npm ci

Common pitfalls

SymptomChanging a single line of code reinstalls all dependencies and the build is very slow.

CauseCOPY . . comes before installing dependencies, so code changes bust the cache of the install layer.

FixCOPY package*.json ./ first, then RUN npm ci, and only after that COPY the remaining code.

SymptomThe container takes ten seconds to stop.

CauseIt uses the shell form of CMD, and the shell as PID 1 does not forward signals to child processes.

FixSwitch to the exec form CMD ["node", "app.js"] so the app receives SIGTERM directly as PID 1.

SymptomArguments passed to docker run override the startup program specified in the image.

CauseCMD was used instead of ENTRYPOINT, so arguments after run replace CMD entirely.

FixPin the program with ENTRYPOINT and give defaults via CMD only: ENTRYPOINT ["node"] plus CMD ["app.js"].

SymptomA password is visible in plaintext in the image and stays even after deletion.

CauseSecrets passed via --build-arg remain in the build history and can be dug out even if a later layer deletes them.

FixUse BuildKit secrets (RUN --mount=type=secret); images already pushed need their secrets rotated.

SymptomThe image is several GB, far larger than expected.

CauseA single-stage build leaves the compile toolchain and source in the final image.

FixSwitch to a multi-stage build: a builder stage installs deps and compiles, and the final stage only COPYs the artifacts.

SymptomFiles in the container are owned by root, and the app process hits permission errors reading/writing its own directory.

CauseCOPY writes files into the image owned by root by default.

FixUse COPY --chown=node:node . . and pair it with USER to run as a non-root user.

Symptomnode_modules was bundled into the image and host-platform binaries do not run in the container.

CauseThe build context did not exclude node_modules via .dockerignore.

FixExclude node_modules in .dockerignore so the image installs the matching platform build with npm ci.

SymptomThe container exits immediately on start and the logs give no clue.

CauseThe startup command path is wrong or the file is not executable, and the Dockerfile itself reports no error.

FixEnter the container with docker run --rm -it <image> sh and run it by hand; check that WORKDIR and the CMD path match.

Troubleshooting

  1. 1Enter the container to debug a failed startup by hand.

    docker run --rm -it <image> sh

    If the image is not interactive, add --entrypoint sh; running the startup command once by hand reveals the real error.

  2. 2Improve build cache hits.

    COPY package*.json ./

    Keep the dependency manifest in its own layer and COPY code only after dependencies are installed, so code changes no longer trigger reinstallation.

  3. 3Multi-stage build taking only artifacts.

    COPY --from=builder /app/dist ./dist

    The final image contains no source or toolchain; size typically drops by an order of magnitude.

  4. 4BuildKit cache mount to speed up installs.

    RUN --mount=type=cache,target=/root/.npm npm ci

    The npm package cache is reused across builds, far faster than re-downloading per layer; requires BuildKit enabled.

  5. 5Confirm what the final image contains.

    docker history <image>

    Look at size by layer to pinpoint exactly which RUN / COPY ballooned the image.

Command Examples

Node multi-stage build: full toolchain at build time, artifacts only at runtime

FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist CMD ["node", "dist/index.js"]

COPY the dependency files first, then npm ci, so editing business code only invalidates the source layer while the dependency layer keeps hitting cache. This layer ordering directly determines build time.

Output

STEP 5/12 : RUN npm run build
 ---> Running in 8f3a1c
> app@1.0.0 build
> vite build
vite v5.0.0 building for production...
✓ 1423 modules transformed.
dist/index.html                   0.46 kB
Successfully built in 12.34s

Keep the build context small with .dockerignore

# .dockerignore node_modules .git *.log .env dist

Without .dockerignore the whole node_modules is sent into the build context, which runs to hundreds of MB and slows every build — plus there is a risk of baking .env into the image.

Scope difference between ARG and ENV

ARG VERSION=1.0 # build-time only, absent from the image ENV NODE_ENV=production # available at both build and run time RUN echo $VERSION $NODE_ENV

A variable defined by ARG does not automatically enter subsequent RUN environments; ENV is needed to keep it in the image. Use ARG for a build-stage version number, ENV for config the runtime reads.

Output

1.0 production

Common Pitfalls

  • Secrets baked into ARG or ENV remain in the image layer permanently — deleting them in a later layer does not hide them from docker history.
  • Bad COPY ordering invalidates the dependency cache, turning a seconds-long build into minutes on every code change.
  • A single-stage build keeps the entire toolchain and source in the final image, easily adding hundreds of MB; use multi-stage and copy only artifacts.
  • Running as root means a container escape immediately yields root; create a non-root user and switch with a USER instruction.
  • CMD is replaced wholesale by arguments after docker run, which loses the program path; fix the program with ENTRYPOINT.

Tips

  • Each RUN/COPY/ADD instruction creates a new layer — merge RUN commands to reduce layer count.
  • Multi-stage builds greatly shrink the final image, ideal for compiled languages like Go, Rust, Java.
  • Use .dockerignore to exclude node_modules, .git, .env and other files not needed for the build.
  • CMD vs ENTRYPOINT: CMD can be overridden by docker run arguments; ENTRYPOINT is fixed.
  • Prefer the exec form ["cmd","arg"] over the shell form — it receives SIGTERM correctly and avoids stop timeouts.
  • Don't pass secrets via --build-arg; they persist in the image build history. Use BuildKit secrets instead.

FAQ

Why does the order of COPY instructions in a Dockerfile matter?

Every RUN, COPY or ADD produces a cached layer, and any change invalidates everything below it. Copying package*.json first and running npm ci afterwards keeps the dependency layer cached, so editing application code only invalidates the source layer. In the reverse order, every code change forces a full dependency reinstall and turns a few-second build into minutes.

What problem does multi-stage building solve?

It separates build-time from runtime dependencies: the builder stage installs the full toolchain and compiles, while the final stage FROMs a slim base and copies in artifacts with COPY --from=builder. Compilers, headers and dev dependencies never reach the final image. The effect is most dramatic for Node projects, where the runtime image ends up holding only dist and node_modules.

What is the difference between ARG and ENV?

ARG is a build-time parameter passed with --build-arg and it does not exist once the build finishes. ENV is both a build-time and a runtime environment variable and is baked into the image. Use ARG for one-off values like a stage version tag, and ENV for anything the running container must see, such as NODE_ENV. A common gotcha: an ARG value is not automatically available to later RUN steps unless you also declare it as ENV.

How should CMD and ENTRYPOINT be combined?

ENTRYPOINT fixes the executable and is not easily replaced by arguments after docker run, while CMD supplies the default arguments and is substituted by them. The usual pattern is ENTRYPOINT ["node"] with CMD ["app.js"], so the default run is node app.js while docker run image other.js becomes node other.js. If the whole command lives in CMD, the program itself is lost when it is overridden.

Should I use COPY or ADD?

Use COPY for ordinary file copying — its behavior is explicit. ADD additionally fetches a URL and auto-extracts local tarballs. Reserve it for those two cases, such as ADD package.tar.gz /app/ when you want extraction; otherwise prefer COPY to avoid surprises from implicit unpacking. Remember to pair the build with a .dockerignore excluding node_modules, .git and similar.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us