Backend / Docker / 10_add_vs_copy.md

ADD vs COPY

Updated 2 interview angles 2 min read source
On this page5
  1. What each one did
  2. Remote files: pin them
  3. Useful COPY options
  4. Related
  5. Interview angle

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

dockerfile
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/LICENSE
text
add_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; COPY it 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-root USER later.
  • Git URLs (ADD https://github.com/org/repo.git#v1.2 /src) clone a ref, without the .git directory unless --keep-git-dir=true.

Remote files: pin them

dockerfile
ADD --checksum=sha256:3c30de7d... https://.../README.rst /opt/README.rst
text
wrong 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.

Interview angle 2

  • “ADD or COPY?” - COPY by default: it only copies. ADD extracts 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, where RUN curl stays cached on its command text.
  • “What ends up in /opt after ADD release.tar.gz /opt/?” - the extracted contents, not the archive. From an https URL, the archive itself, unextracted, with mode 0600.