Dockerfile cheat sheet
Every Dockerfile instruction with what it actually does, plus the build flags worth remembering. Examples marked with a play button were executed on Ubuntu 26.04 with Docker 29.7.2 - nothing here shows output that was not captured.
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- BuildKitv0.32.2
- Architectureamd64
- Commands33
- Reviewed22 August 2026
Base image and stages
-
FROM image:tagStart a new build stage from a base image. Pin a real version - :latest is a moving target.
-
FROM image:tag AS nameName a stage so a later stage can COPY --from it.
bash Example session cat DockerfileFROM alpine:3.22 AS buildRUN gcc -static -O2 -o hello hello.c FROM scratch AS runtimeCOPY --from=build /src/hello /hello -
FROM scratchAn entirely empty base - no shell, no libc. Only viable for statically linked binaries.
bash Example session docker images --filter reference=cg-multi --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"REPOSITORY:TAG SIZEcg-multi:1 112kBcg-multi:builder 253MB -
FROM --platform=$BUILDPLATFORM image AS buildPin a stage to the machine doing the building so compilation stays native during a cross-platform build.
Files and filesystem
-
COPY src destCopy from the build context into the image. Cannot reach outside the context.
-
COPY --from=stage src destCopy from an earlier stage instead of the context. The basis of multi-stage builds.
-
COPY --chown=user:group src destSet ownership as part of the copy, avoiding a separate chown layer.
-
ADDLike COPY but also expands local tar archives and fetches URLs. Prefer COPY unless you need those - ADD's extra behaviour surprises people.
-
WORKDIR /pathSet the directory for later instructions and for the running container. Creates it if absent.
Read it back with the single inspect in this section.
-
VOLUME /pathCautionDeclare a mount point. Docker creates an anonymous volume there at run time if nothing is mounted - which is easy to forget and leaves unnamed volumes behind.
Execution
-
RUN commandExecute at BUILD time and commit the result as a layer. Clean up within the same RUN or the mess ships.
bash Example session docker history cg-hello:1.0 --format "table {{.CreatedBy}}\t{{.Size}}"CREATED BY SIZERUN /bin/sh -c apk add --no-cache curl # bui… 5.27MB -
CMD ["exe","arg"]Default command at RUN time. Replaced entirely by anything you pass to docker run.
-
ENTRYPOINT ["exe"]The command that always runs. Arguments from docker run are appended to it rather than replacing it.
bash Example session docker run --rm cg-inst:1running 0.0.0 as appuser -
HEALTHCHECK CMD commandLet Docker decide whether the container is healthy, rather than assuming Up means ready.
-
SHELL ["/bin/bash","-c"]Change the shell used by shell-form RUN, CMD and ENTRYPOINT.
Variables
-
ARG NAME=defaultBuild-time only. Not present in the running container unless an ENV copies it.
bash Example session docker build --build-arg APP_VERSION=2.5.0 -t cg-inst:2 .docker run --rm cg-inst:2running 2.5.0 as appuser -
ENV NAME=valueBaked into the image and visible to the process. Overridable per container with -e.
bash Example session docker run --rm -e APP_VERSION=9.9.9 cg-inst:1running 9.9.9 as appuser -
ARG for secretsWhole-hostDo not. Build args are recorded in the image history and readable by anyone with the image.
Use BuildKit build secrets instead: RUN --mount=type=secret,id=mytoken. A secret passed as ARG survives in the image metadata even if the file is deleted in a later layer.
Identity and metadata
-
USER nameRun later instructions and the container process as this account. The cheapest security improvement available.
Read it back with the single inspect in this section.
-
EXPOSE 8080Metadata only. Documents an intended port and publishes nothing - you still need -p at run time.
Read it back with the single inspect in this section.
-
LABEL key=valueAttach metadata. Prefer the org.opencontainers.image.* keys so tooling can read them.
Read it back with the single inspect in this section.
-
STOPSIGNAL SIGTERMWhich signal stops the main process. Change it only if your process ignores the default.
-
docker image inspect IMAGE --format "user={{.Config.User}} ..."Read USER, ENV, EXPOSE and LABEL back off a built image in one command - which is how you check the Dockerfile did what you meant.
bash Example session docker image inspect cg-inst:1 --format "user={{.Config.User}} env={{.Config.Env}} exposed={{.Config.ExposedPorts}} labels={{.Config.Labels}}"user=appuser env=[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin APP_HOME=/app APP_VERSION=0.0.0] exposed=map[8080/tcp:{}] labels=map[org.opencontainers.image.title:cg-demo]
Build flags
-
docker build -t name:tag .Build from the Dockerfile in the context directory and tag the result.
-
docker build -f path/Dockerfile .Use a Dockerfile outside the context root. The context is still the final argument.
-
docker build --target stage .Build only up to a named stage. How you inspect an intermediate builder.
bash Example session docker build --target build -t cg-multi:builder .#10 DONE 4.3s -
docker build --no-cache .CautionIgnore every cached layer. Slow on purpose - use to prove a build is reproducible.
-
docker build --progress=plain .Unfolded build log. Use it to see which layers were CACHED and which rebuilt.
-
docker buildx build --platform a,b --push .Build several architectures and push them as one manifest. Needs a docker-container builder.
bash Example session docker buildx imagetools inspect localhost:5000/cg-bx:multiMediaType: application/vnd.oci.image.index.v1+json Platform: linux/amd64 Platform: linux/arm64 Platform: linux/arm/v7
Patterns worth memorising
-
Manifest first, then install, then sourceKeeps the dependency install cached across source changes. The single highest-value ordering rule.
bash Example session docker build -t cg-ctx:demo . 2>&1 | grep -c CACHED3 -
.dockerignore at the context rootCuts what transfers and what lands in the image. Measured here at 3.00MB down to 138B.
bash Example session docker build --no-cache -t cg-ctx:withdockerignore .#3 transferring context: 57B done#6 transferring context: 138B done -
Clean up inside the same RUNA file deleted in a later layer still ships in the layer that added it. apk add --no-cache and rm in the same RUN are the fix.
-
Build tools in a builder stage onlyShip the artifact, not the compiler. 253MB of toolchain became a 112kB runtime image.
No command matches that search.