ADD vs COPY
COPY copies files from the build context (or from another stage or image)
into the image, and does nothing else. ADD does the same plus three extras:
it extracts local tar archives, downloads remote URLs, and clones Git
repositories. Use COPY by default, because it does exactly what it says;
use ADD when you want one of the extras, deliberately.
Behaviour below was checked with BuildKit (Docker Engine 29.8).
What each one did
FROM busybox:1.37
ADD app.tar.gz /opt/add_tar/
COPY app.tar.gz /opt/copy_tar/
ADD app.zip /opt/add_zip/
ADD https://raw.githubusercontent.com/python/cpython/v3.14.0/LICENSE \
/opt/remote/LICENSEadd_tar: -rw-r--r-- readme.txt unpacked
copy_tar: -rwxr-xr-x app.tar.gz copied as a file
add_zip: -rwxr-xr-x app.zip not extracted
remote: -rw------- LICENSE downloaded, 0600- Local tar archives (gzip, bzip2, xz, zstd) are extracted silently. That
surprises people who wanted the archive itself;
COPYit instead. - Zip files are never extracted by
ADD. - Remote files are downloaded, not extracted (unless
--unpack=true), and land readable only by root (0600), which breaks a non-rootUSERlater. - Git URLs (
ADD https://github.com/org/repo.git#v1.2 /src) clone a ref, without the.gitdirectory unless--keep-git-dir=true.
Remote files: pin them
ADD --checksum=sha256:3c30de7d... https://.../README.rst /opt/README.rstwrong checksum: ERROR: failed to solve: digest mismatch sha256:3c30de7d...A remote ADD without a checksum is a mutable input: the URL can serve
different bytes tomorrow. With --checksum the build fails on any change,
and BuildKit checks the remote content on each build.
Compare
RUN curl .../latest -o tool, which is cached on its command text and keeps
shipping whatever it downloaded the first time
(Docker image layers and build caching). This is why
Docker’s own guidance now prefers ADD --checksum over curl in a RUN for
fetching a remote file.
Useful COPY options
| Option | Does |
|---|---|
--from=build |
copy from a stage, or an image |
--chown=1000:1000 |
set ownership without an extra RUN chown layer |
--chmod=0755 |
set permissions |
--link |
independent layer, survives base-image changes |
--exclude=*.pyc |
skip matching files |
COPY --from=ghcr.io/astral-sh/uv:0.9.0 /uv /bin/uv is the tidy way to bring
in a single binary from a pinned image, with no download in a RUN.
Related
Interview angle 2
- “ADD or COPY?” -
COPYby default: it only copies.ADDextracts local tar archives, downloads URLs and clones Git repositories, which is surprising when unintended.- Follow-up: “When is ADD the right choice now?” - fetching a remote file
with
--checksum: a changed file fails the build, whereRUN curlstays cached on its command text.
- Follow-up: “When is ADD the right choice now?” - fetching a remote file
with
- “What ends up in
/optafterADD release.tar.gz /opt/?” - the extracted contents, not the archive. From an https URL, the archive itself, unextracted, with mode 0600.