COPY copies files from the build context into the image and does nothing else. ADD does the same but also downloads URLs and automatically extracts local tar archives. Using ADD for ordinary files hides that extra behaviour: if a source later becomes a tar archive, it is unpacked rather than copied. Docker's own guidance is to use COPY unless you need what ADD adds.
openrouting — dockerfile explainer
$ explain Dockerfile! ADD used for a local file lowline 5fix: see below$
What the free Dockerfile explainer reports on the risky example below.
Why it matters
Readers expect a copy. With ADD, what lands in the image depends on what the source turns out to be, which makes the Dockerfile harder to review.
Auto-extraction applies to tar archives in recognised compression formats such as gzip, bzip2 and xz. Zip files are not extracted.
The explainer flags ADD for local paths that are not tar archives or git sources at low severity. Remote URLs are covered by a separate rule.
Risky and safer Dockerfile
RiskyDockerfile
FROM node:22.11-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
ADD src ./src
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]
SaferDockerfile
FROM node:22.11-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]
How to fix it
Replace ADD with COPY for local files and directories. The syntax and flags such as --chown are the same.
Keep ADD only where you want its extra behaviour: extracting a local tar archive, or a remote download verified with --checksum.
When it's acceptable
Extracting a local tarball, e.g. `ADD rootfs.tar.gz /`. The explainer doesn't flag local tar archives.
Check your own Dockerfile
The risky example is loaded below. Paste your own file to check it for this and every other Dockerfile rule. It runs in your browser; nothing is uploaded.