Docker COPY vs ADD: How to Choose the Right Instruction
Master Docker COPY vs ADD to optimize your build cache and improve security. Learn why COPY is usually the safer, more predictable choice for your images.
We’ve all been there: you make a tiny change to a configuration file, but your entire Docker build takes five minutes because the cache invalidated at the wrong layer. Understanding the nuance of Docker COPY vs ADD is one of the quickest ways to stop wasting time on redundant image layers.
At first glance, both instructions move files from your host machine into the container image. However, they behave differently under the hood, and those differences are the root cause of many "why is my cache invalid?" headaches.
The Core Difference: COPY vs ADD
The rule of thumb is simple: always use COPY unless you have a specific, documented reason to use ADD.
COPY is straightforward. It takes a file or directory from your local context and places it into the container. It does exactly what it says on the tin.
ADD, on the other hand, is a "smart" instruction. It supports two features that COPY doesn't:
- Remote URLs: You can pass a URL as the source, and it will download the file into the image.
- Automatic Extraction: If the source is a local tar archive in a recognized compression format (gzip, bzip2, xz), it will automatically unpack it into the destination.
Why ADD causes cache issues
When you use ADD to fetch a remote URL, Docker has to check if the file at that URL has changed. It does this by fetching the Last-Modified header or by calculating a checksum. If the remote server doesn't provide consistent metadata, your Docker build cache may invalidate unexpectedly.
Even worse, if you use ADD for a local tarball, Docker checks the file's modification time. If you update the file, the cache invalidates. While this is often intended, it’s easy to accidentally trigger cache invalidation if your build process modifies the file timestamp without changing the content.
Dockerfile Optimization and Security
Effective Dockerfile optimization isn't just about speed; it's about reducing the attack surface of your final image.
Using ADD with remote URLs is a common Docker security best practice violation. When you ADD a URL, you are essentially performing a blind network request during the build phase. If that remote resource is compromised or intercepted, you’ve just injected malicious code into your image.
Instead, perform the download inside a RUN command:
Dockerfile# The wrong way (Less secure, harder to cache) ADD https://example.com/big-file.tar.gz /tmp/ # The right way (More secure, better cache control) RUN curl -fSL https://example.com/big-file.tar.gz -o /tmp/big-file.tar.gz && \ tar -xzf /tmp/big-file.tar.gz -C /opt/app && \ rm /tmp/big-file.tar.gz
By using RUN, you can verify the integrity of the file using a checksum before extracting it. You also keep the layer clean by deleting the archive immediately after extraction. If you're struggling with slow incremental builds, make sure you've also audited your build context, as I’ve covered in my guide on Mastering Docker Build Context: Efficiency and Security Tips.
Comparison Table
| Feature | COPY | ADD |
|---|---|---|
| Local file copy | Yes | Yes |
| Remote URL support | No | Yes |
| Auto-extract tarballs | No | Yes |
| Predictable caching | High | Low |
| Security | Safe | Risky |
When to use what?
- Use COPY: 99% of the time. If you are moving source code, config files, or artifacts that are already on your local disk,
COPYis the standard. It’s predictable, fast, and doesn't do "magic" that can break your cache. - Use ADD: Only when you need to extract a local tar archive automatically into a directory. Even then, I personally prefer
COPYfollowed byRUN tar -xfbecause it makes the build steps explicit and easier to debug.
If your project is growing in complexity, you might find that build bottlenecks are just one part of a larger performance puzzle; sometimes you need Laravel Bug Fixes, Maintenance & Optimization to address deeper architectural issues.
Final Thoughts
I’ve spent hours debugging build pipelines only to realize an ADD instruction was re-downloading a file every single time because the remote server changed its header format. It’s a frustrating lesson, but it highlights why being explicit in your Dockerfile matters.
If you're still seeing issues with slow builds, check out my notes on Docker build cache debugging: Fix slow incremental builds to see if you're hitting layer invalidation elsewhere. Keep your instructions simple, stick to COPY, and treat ADD as an exception rather than a tool for everyday tasks.
Frequently Asked Questions
Does COPY support wildcards?
Yes. COPY supports standard Go-style wildcards, such as COPY src/*.json /app/.
Why does my image size increase when using ADD?
If you ADD a tarball, it extracts it. If you don't remove the original tarball in the same RUN layer, the file remains in the image, effectively doubling the space taken by that file.
Is ADD technically deprecated?
No, ADD is not deprecated. It remains a part of the official Dockerfile specification, but it is considered a "legacy" feature for most workflows due to the security and predictability concerns discussed above.

