ARG VERSION=1.36
FROM busybox:$VERSION
ARG VERSION
WORKDIR /app
COPY --chown=1000:1000 . /app
RUN echo "build $VERSION" > /app/build.txt
ENV APP_ENV=production
EXPOSE 8080
| Instruction | Meaning |
|---|---|
FROM |
Starts a new build stage and picks the base image; each FROM drops the previous stage's state |
RUN |
Runs a command at build time; the result becomes a layer |
WORKDIR |
Sets the working directory for later instructions; created if missing |
COPY |
Copies files from the build context into the image; --chown sets the owner |
EXPOSE |
Documents the port the app listens on (TCP by default, 80/udp for UDP) |
ENV |
An environment variable available at build and at container run time |
ARG |
A variable that only exists at build time, passed with --build-arg |
flowchart LR CTX["Build context<br/>Dockerfile + files"] --> FROM["FROM busybox<br/>base image"] FROM --> WD["WORKDIR /app"] WD --> CP["COPY . /app<br/>new layer"] CP --> RUN["RUN build step<br/>new layer"] RUN --> IMG["Image demo:1.36<br/>ENV and EXPOSE as metadata"] IMG -- "docker run -p 8080:8080" --> C["Container<br/>port published"] %% step CTX: docker build sends the build context; COPY can only read files inside it. %% step FROM: FROM picks the base image and starts a stage; ARG before FROM is usable only in this line. %% step WD: WORKDIR sets the directory for the instructions that follow. %% step CP: COPY adds files from the context as a new layer. %% step RUN: RUN executes at build time and its result becomes another layer. %% step IMG: ENV and EXPOSE are stored as image metadata; EXPOSE only documents the port. %% step C: The port is reachable from the host only when docker run publishes it with -p.
Easy to get wrong
- EXPOSE does not publish a port. To reach it from the host you still need
docker run -p 8080:8080. - ARG before FROM can only be used in the
FROMline. To use it inside the stage, declareARG VERSIONagain (no value needed). - ARG vs ENV:
ARGis gone after the build;ENVstays in the container. Never put secrets inARGorENV, because both can be seen withdocker historyordocker image inspect. - Relative WORKDIR builds on the previous one:
/a→b→cgives/a/b/c. - RUN shell form (
RUN apt-get update) runs through/bin/sh -c, so variables such as$HOMEare expanded. Exec form (RUN ["echo", "$HOME"]) skips the shell and prints$HOMEliterally. COPYcan only take files inside the build context;COPY ../xfails.
docker build --build-arg VERSION=1.36 -t demo:1.36 .
More instructions
| Instruction | Meaning |
|---|---|
ADD |
Like COPY, but also extracts local tar archives and can fetch URLs; prefer COPY unless you need those |
USER |
Sets the user for later RUN, CMD, and ENTRYPOINT; use a non-root user for the final image |
LABEL |
Metadata such as org.opencontainers.image.source or a maintainer |
VOLUME |
Marks a path as a volume so its data lives outside the container layer |
HEALTHCHECK |
Command Docker runs to judge whether the app is healthy |
SHELL |
Changes the shell used by shell-form instructions, for example to bash or PowerShell |
STOPSIGNAL |
Signal sent by docker stop |
Running as non-root
RUN addgroup -S app && adduser -S app -G app
USER app
A process running as root inside a container is root on the host if it ever escapes, so dropping privileges is one of the cheapest security wins. Many official images ship a ready user, such as node in Node.js images.
Secrets during the build
Never COPY a credentials file or pass a token through ARG. With BuildKit, a secret can be mounted only for one RUN step and never stored in a layer:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
Build it with docker build --secret id=npmrc,src=$HOME/.npmrc ..
.dockerignore
A .dockerignore file next to the Dockerfile keeps files out of the build context: .git, node_modules, local .env files, and test output. A smaller context uploads faster, invalidates the cache less often, and avoids leaking secrets into the image through COPY . ..
Heredocs
Recent Dockerfile syntax allows multi-line scripts in one instruction:
RUN <<EOF
set -e
apt-get update
apt-get install -y --no-install-recommends curl
rm -rf /var/lib/apt/lists/*
EOF