COPY and ADD in Docker: Two Ways to Get Files Into an Image, and Why They’re Not the Same

COPY and ADD in Docker: Two Ways to Get Files Into an Image, and Why They're Not the Same

Almost every Dockerfile ever written contains at least one line that moves a file from the outside world into the image being built. Two instructions can do this job — COPY and ADD — and picking the wrong one out of habit is one of the most common, and most avoidable, sources of bloated, unpredictable images.

Imagine two ways to get a piece of furniture into a new apartment. The first way is simple and predictable: you personally carry a specific box from a specific shelf in your garage and place it exactly where you want it in the new home — nothing more, nothing less. The second way is more like hiring a delivery service that will not only carry local boxes for you, but will also, if you hand it a receipt instead of a box, drive to the store, buy the item, unbox it, and set it up for you automatically. Both methods get furniture into the apartment. One is simple and does exactly what it says; the other is more powerful, but that extra power comes with surprises if you didn’t expect it. This is essentially the relationship between Docker’s two file-transfer instructions: COPY, the simple and predictable option, and ADD, the more powerful option with extra built-in behaviors that catch many engineers off guard. This article explains exactly what each instruction does, why Docker’s own documentation recommends one over the other in almost every situation, and the specific, narrow cases where the more powerful option actually earns its keep.

1Core Concepts

Before comparing the two instructions, it helps to understand the basic problem both of them exist to solve: getting files from outside a container image into the filesystem that image will contain.

Why a Dockerfile Needs to Move Files At All

A Docker image starts, in most cases, from a base image containing an operating system and perhaps a language runtime, but no application code. Somewhere in the process of building a custom image, the actual application source code, configuration files, or static assets have to make their way from the machine performing the build into the image’s own filesystem layers. This is a fundamentally different problem than running a command inside the image, because the files being copied usually live outside the container entirely, sitting on the disk of whichever machine is running docker build.

What COPY Does

The COPY instruction does exactly one job: it copies files or directories from the build context — the folder you pointed docker build at — into a specified location inside the image being built. It is a direct, literal file copy operation, comparable to dragging a file from one folder to another on your own computer. It does not fetch anything from the internet, and it does not automatically unpack compressed archives.

Everyday Analogy

COPY is like a librarian who, when asked to place a specific book on a specific shelf, does precisely that and nothing else. Hand the librarian a sealed box, and it goes on the shelf exactly as a sealed box — the librarian doesn’t decide to open it, and doesn’t go check whether a newer edition exists somewhere else. What you hand over is exactly what ends up in place.

What ADD Does

The ADD instruction can do everything COPY does, plus two additional, automatic behaviors: if the source is a recognized compressed archive format (such as a tar file, optionally gzip or bzip2 compressed) located in the build context, ADD automatically extracts its contents into the destination directory instead of copying the archive file itself; and if the source is a URL rather than a local file, ADD will download that remote file and place it in the image.

i
The One-Sentence Summary

COPY does one predictable thing: copy local files as-is. ADD does that same thing, plus automatic archive extraction and remote URL fetching — extra convenience that comes with extra, often unwanted, surprises.

Why This Distinction Matters From Day One

Because ADD’s extra behaviors are triggered automatically based on what the source looks like, rather than through an explicit flag the Dockerfile author chooses, its behavior can be harder to predict at a glance. A teammate reading a Dockerfile with a COPY instruction knows immediately and unambiguously what will happen. A teammate reading an ADD instruction has to first check whether the source is a plain file, an archive, or a URL, because each one leads to meaningfully different behavior in the final image.

2Architecture & Components

Both instructions sit inside Docker’s broader build architecture, particularly the concept of the build context and the layered filesystem that every image is made of.

The Build Context

When a Dockerfile is built, Docker first gathers every file in a specified directory — commonly the current folder — into something called the build context, and sends that entire context to the build engine before a single instruction runs. Both COPY and ADD can only reference files that exist inside this build context; neither instruction can reach outside of it to grab an arbitrary file from elsewhere on the host machine’s filesystem, which is a deliberate security boundary preventing a Dockerfile from accidentally, or maliciously, pulling in unrelated files it was never meant to see.

flowchart LR
    A["Host Machine Folder
(the build context)"] -->|docker build .| B["Build Context
Sent to Build Engine"] B --> C{"Dockerfile Instruction"} C -->|COPY src dest| D["Direct file copy
into new image layer"] C -->|"ADD src.tar.gz dest"| E["Automatic extraction
into new image layer"] C -->|"ADD https://... dest"| F["Remote download
into new image layer"] D --> G["Final Image Filesystem"] E --> G F --> G

Fig 2.1 — Both instructions pull from the same build context, but ADD branches into extra automatic behaviors based on the source type

Layers and Image Size

Every COPY or ADD instruction creates a new, immutable layer in the resulting image. Understanding this matters directly for image size and build speed: if a large file is copied in an early layer and later removed in a subsequent instruction, the bytes from that file are still physically present in the earlier layer and still count toward the final image’s total size, because layers are additive and earlier ones are never silently rewritten by later ones.

Simple

COPY

Direct, literal copy of files or directories from the build context into the image. No extraction, no network access.

Extended

ADD (local archive)

Automatically detects and extracts recognized compressed archive formats from the build context into the destination.

Extended

ADD (remote URL)

Downloads a file from a URL directly into the image during the build, without needing a separate download step.

Shared

Build Context Boundary

Neither instruction can reach files outside the build context passed to docker build.

Ownership and Permissions

Both instructions support an option to set the ownership (user and group) of the copied files as they land inside the image, which matters a great deal for images designed to run as a non-root user — a very common security best practice. Without explicitly setting ownership, copied files typically land owned by the root user by default, which can cause a permission error later if the container’s main process is deliberately configured to run as a different, non-root user.

3Internal Working

Understanding exactly what happens, step by step, during each type of copy operation clarifies why the two instructions behave so differently in practice.

How COPY Processes a Request

When the build engine reaches a COPY instruction, it looks up the specified source path or paths inside the already-collected build context, verifies they exist, and writes their exact bytes into the destination path inside a new image layer. There is no interpretation of file type, no decision-making about whether something looks like an archive, and no network activity of any kind. What goes in is exactly what comes out, one-to-one.

How ADD Processes a Local Archive

When ADD encounters a source file in the build context that matches a recognized compressed archive format, it does not simply copy that archive file into the destination as a single compressed file. Instead, it automatically unpacks the archive’s contents directly into the destination directory, as though someone had manually extracted it and then copied each resulting file over individually. This automatic extraction only applies to local files in the build context — if the exact same archive were instead placed at a remote URL, ADD’s URL-handling path does not perform this same automatic extraction, which is a subtle but important asymmetry to remember.

!
A Frequently Missed Detail

ADD’s automatic extraction behavior applies specifically to local archives from the build context. A remote URL pointing at an archive file is simply downloaded as-is, as a single compressed file, without being automatically unpacked. Many engineers assume the two cases behave identically, and are surprised to find a compressed file sitting untouched inside their image after using ADD with a URL.

How ADD Processes a Remote URL

When the source given to ADD is a URL rather than a local path, the build engine performs an HTTP request during the image build itself, downloads the resulting file, and places it at the specified destination inside the new layer. This means the network has to be reachable at build time for this step to succeed, and the exact bytes downloaded depend entirely on what that remote URL happens to serve back at the moment the build runs — which may not be the same content every time, especially if the URL points at something that changes over time.

sequenceDiagram
    participant Ctx as Build Context
    participant Build as Build Engine
    participant Net as Remote Server
    participant Img as New Image Layer
    Build->>Ctx: Read local source for COPY
    Ctx-->>Build: Return raw file bytes
    Build->>Img: Write bytes unchanged
    Note over Build,Img: COPY: no branching logic
    Build->>Ctx: Read local source for ADD
    alt Source is a recognized archive
        Ctx-->>Build: Return archive bytes
        Build->>Build: Extract archive contents
        Build->>Img: Write extracted files
    else Source is a URL
        Build->>Net: HTTP GET request
        Net-->>Build: Return remote file bytes
        Build->>Img: Write downloaded file as-is
    end
        

Fig 3.1 — COPY follows one straight path; ADD branches based on what kind of source it was given

Why Reproducibility Suffers With Remote ADD

Because a URL-based ADD performs a live network request at build time, the exact same Dockerfile can theoretically produce a different image on two separate build runs if the content at that URL changes in between — something that would never happen with a COPY instruction referencing a fixed, version-controlled local file, since the build context itself does not silently change between runs unless the developer deliberately changes it.

4Data Flow & Lifecycle

Tracing a file’s journey from a developer’s own repository all the way into a running container makes clear exactly where COPY and ADD sit in the broader build-and-deploy lifecycle.

1

Source Files Committed

Application code, configuration, and static assets live in version control, exactly as any team member can review and inspect them.

2

docker build Gathers the Context

The build command bundles the specified folder’s contents into the build context sent to the build engine.

3

COPY or ADD Executes

Files move from the context into a new image layer, with ADD potentially extracting an archive or fetching a URL along the way.

4

Layer Caching Decision

Docker checks whether the copied content changed since the last build to decide whether this layer, and every layer after it, can be reused from cache.

5

Image Published

The finished image, containing every copied file baked permanently into its layers, is pushed to a registry.

6

Container Runs With Baked-In Files

Every container started from the image already contains the copied files; no copy operation happens again at container run time.

Build Cache and Instruction Ordering

Docker’s build cache checks whether the content being copied has changed since the previous build; if it hasn’t, Docker can reuse the previously built layer instead of repeating the copy operation, which significantly speeds up repeated builds. This is precisely why experienced Dockerfile authors deliberately order their COPY instructions carefully — copying files that rarely change (such as a dependency manifest) before files that change constantly (such as application source code), so that a small source code edit doesn’t invalidate and force a rebuild of an expensive dependency installation step that didn’t actually need to change.

Real Pattern: Separating Dependency Files From Source Code

A common, high-value pattern copies only a project’s dependency manifest first, runs the dependency installation step, and only then copies the rest of the application source code in a later instruction. Because the dependency manifest changes far less often than application code, this ordering means routine code changes don’t force a full, slow dependency reinstall on every single build — only an actual dependency change does.

Where the URL-Fetching Behavior Breaks This Lifecycle

Because a URL-based ADD performs a live network call, its build cache behavior is comparatively less predictable than a local COPY — the cache generally keys off the instruction’s declared source, not off freshly re-checking the remote content’s current state on every single build, which means a changed remote file might not automatically trigger a fresh download unless the build cache is deliberately invalidated. This is one more reason experienced teams generally prefer downloading remote files in a separate, explicit, controlled step, rather than relying on ADD’s built-in URL behavior.

5Advantages, Disadvantages & Trade-offs

Neither instruction is universally “better” — each represents a different trade-off between simplicity and convenience.

COPY — Advantages

  • Behavior is completely predictable: exactly what you specify is exactly what happens
  • No hidden network calls, keeping builds reproducible and offline-friendly
  • Easier for a reviewer to reason about at a glance, with no branching logic to consider
  • Officially recommended default for the vast majority of Dockerfile authoring

ADD — Trade-offs

  • Automatic archive extraction can be genuinely convenient for a small, specific set of use cases
  • Remote URL fetching introduces a live network dependency into the build process itself
  • Behavior depends on inferring the source type, making it less immediately obvious to a reader
  • Can silently produce a larger or differently structured image than a reader expects

Why Official Guidance Leans Toward COPY

Docker’s own documentation explicitly recommends COPY for the majority of cases specifically because of its predictability: a Dockerfile is a piece of infrastructure that is often read, reviewed, and modified by people other than its original author, sometimes years later. An instruction whose behavior can be fully understood without needing to check the nature of its source file is significantly easier to maintain safely over that long lifespan than one requiring extra context to interpret correctly.

Where ADD’s Extra Power Genuinely Helps

The one case where ADD’s automatic archive extraction is broadly considered a legitimate, even preferred, choice is unpacking a local tarball of application files directly into an image in a single instruction, avoiding a separate manual extraction step elsewhere in the build. Outside of this specific case, and outside of a handful of ADD’s own documented, narrow local-archive scenarios, most style guides and linters actively recommend against reaching for ADD’s remote URL behavior in production Dockerfiles.

6Security

The extra automatic behaviors that make ADD more powerful than COPY are precisely the same behaviors that introduce meaningful security considerations.

The Risk of Fetching Remote Content During a Build

Using ADD with a remote URL means the build process itself reaches out to a server outside of your control, at build time, and trusts whatever content that server returns. If that URL is ever compromised, redirected, or simply serves different content than expected, the resulting image can silently contain unexpected, potentially malicious content, without the Dockerfile itself ever visibly changing. This is a meaningfully larger trust surface than a COPY instruction referencing a file that already lives, reviewed and version-controlled, inside the same repository as the Dockerfile.

Everyday Analogy

Using COPY is like packing your own suitcase yourself, item by item, so you know exactly what’s inside. Using ADD with a remote URL is like asking a stranger at the airport to hand you a sealed bag to check in as your own luggage — it might be exactly what you expect, but you have no way to verify that until it’s already gone through security and onto the plane.

Extraction Behavior and Archive Contents

Automatic extraction of an archive means every file packed inside that archive lands inside the image, whether or not the Dockerfile author fully audited every single file the archive contains. An archive built by an unfamiliar third-party tool or process could, in principle, include far more files than expected, and ADD’s automatic unpacking behavior means those extra files land inside the image without any additional visible step in the Dockerfile drawing attention to them.

Why Explicit, Separate Download Steps Are Often Preferred

Rather than relying on ADD’s built-in URL fetching, many security-conscious teams prefer to perform any necessary downloads as an explicit, separate, auditable step — one that can include verifying a cryptographic checksum of the downloaded file against a known-good value before it is trusted and copied into the image. This extra verification step is something ADD’s built-in URL behavior does not perform automatically, meaning a Dockerfile relying on it alone has no built-in protection against a tampered or unexpectedly different remote file.

PATTERN — CA-01 ANTI-PATTERN
The Problem

A Dockerfile uses ADD with a remote URL to fetch a third-party installer or binary directly into the image, with no checksum verification anywhere in the process.

Why It Fails

If the remote URL is ever compromised, or if its content changes unexpectedly for any reason, the resulting image silently changes too, with nothing in the Dockerfile itself signaling that anything is different. Builds also become dependent on that external server’s availability and integrity indefinitely into the future.

Better Approach

Download the file as an explicit, separate step that verifies a known checksum before the file is trusted, then use COPY to bring the verified file into the image from the local build context, keeping the trust boundary clearly visible in the Dockerfile.

7Monitoring, Logging & Metrics

While COPY and ADD are build-time instructions with no runtime footprint of their own, their effects on image size, build reliability, and reproducibility are all worth actively tracking.

Tracking Image Size Contribution

Because every COPY or ADD instruction adds a new layer, teams that care about lean images regularly inspect which layers contribute the most to an image’s total size, often discovering that an early, forgotten COPY of an unnecessarily large file (such as an entire local dependency cache folder that should have been excluded) is quietly bloating every derived image built from that Dockerfile ever since.

Size

Layer Size Auditing

Regularly reviewing which COPY or ADD layers contribute most to total image size catches accidental bloat early.

Reliability

Build Failure Tracking

Monitoring build failures caused by unreachable remote URLs used with ADD highlights a hidden external dependency worth removing.

Cache

Cache Hit Rate

Tracking how often COPY layers are served from cache versus rebuilt reveals whether instruction ordering is actually optimized.

Drift

Reproducibility Checks

Comparing two builds of the same commit for byte-identical output surfaces any hidden non-determinism, often traced back to a remote ADD source.

A Real Pattern: Flagging Remote ADD Usage in CI

Some organizations configure a linting step in their continuous integration pipeline that specifically scans every Dockerfile for ADD instructions pointing at a URL, flagging them for manual review rather than allowing them to merge automatically. This turns a subtle, easy-to-miss pattern into a visible, actively monitored signal the team can consciously accept or reject on a case-by-case basis.

8Design Patterns & Anti-patterns

A handful of clear, well-established patterns have emerged from years of real-world Dockerfile authorship around exactly when to reach for COPY versus ADD.

Pattern: Default to COPY, Always

The single most widely agreed-upon pattern across the Docker community is to default to COPY for essentially every file transfer, and to reach for ADD only when one of its specific extra behaviors is genuinely needed and deliberately chosen, never out of habit or because an old tutorial happened to use it.

Pattern: Multi-Stage Builds Combined With COPY

In a multi-stage build, a special form of COPY can pull files not from the local build context, but directly from an earlier build stage — for example, copying only a compiled binary out of a large build-tooling stage into a small, final runtime stage. This pattern keeps the final image lean while still allowing the build process itself to use whatever large, heavy tools it needs in an earlier, discarded stage.

Pattern: Reserving ADD Strictly for Local Archive Extraction

When a project genuinely needs to unpack a local tarball as part of its build, using ADD for that single, specific purpose remains a reasonable, well-understood pattern — the key discipline is using it only for that purpose, and continuing to use COPY for every other file in the same Dockerfile, rather than switching entirely to ADD everywhere out of convenience.

PATTERN — CA-02 ANTI-PATTERN
The Problem

A team uses ADD everywhere in their Dockerfiles as a habit, on the belief that it is simply “the more capable version of COPY” and therefore always the safer default choice.

Why It Fails

This inverts the actual trade-off. ADD’s extra capabilities introduce extra, often unintended behavior — an innocuously named file that happens to match a recognized archive extension could be silently extracted instead of copied as-is, changing the final image’s file structure in a way nobody intended or noticed during review.

Better Approach

Default to COPY everywhere, and only switch to ADD for a specific instruction where its automatic extraction behavior is deliberately wanted, documenting why in a nearby comment so future readers understand the choice was intentional.

Anti-pattern: Fetching Application Dependencies With Remote ADD

Some Dockerfiles use ADD with a URL to fetch an entire application dependency, such as a specific tool binary, directly during the build. This bypasses whatever dependency management and version-locking practices a project otherwise follows elsewhere, and ties every future build’s success to that one remote URL remaining available and unchanged indefinitely.

Pattern: Explicit .dockerignore as a Companion to COPY

Because a broad COPY instruction naturally pulls in everything present in its source directory, mature projects pair it with an ignore file that explicitly lists paths to exclude from the build context entirely — things like local dependency caches, editor configuration folders, and version control metadata. This means a simple, broad COPY instruction stays safe and lean, because the unwanted files never even make it into the build context in the first place, rather than relying on the Dockerfile author to remember every exclusion at the moment they write each individual COPY line.

9Best Practices & Common Mistakes

Bringing everything together into a practical checklist for choosing and using these two instructions correctly.

PracticeWhy It Matters
Default to COPY unless a specific ADD behavior is genuinely neededKeeps Dockerfile behavior predictable and easy to review
Copy dependency manifests before application source codeMaximizes build cache reuse on routine code changes
Avoid ADD with remote URLs in production DockerfilesPrevents a live, unverified network dependency at build time
Set explicit file ownership during COPY for non-root containersPrevents permission errors when the process runs as a non-root user
Exclude unnecessary files from the build contextPrevents accidentally copying large, irrelevant files into the image
Document any intentional use of ADD with a short commentSignals to future readers that the extra behavior was a deliberate choice

Common Mistake: Copying an Entire Project Directory Blindly

A frequent mistake is copying an entire project folder into the image with a single broad instruction, without first excluding files that don’t belong in the image at all, such as local development configuration, version control metadata, or large local caches. This routinely bloats the resulting image and can even accidentally copy sensitive local files, such as credentials a developer keeps in an untracked local file, directly into a shareable image.

Common Mistake: Assuming ADD’s Extraction Always Applies

Some engineers mistakenly assume that ADD will extract any archive-like file regardless of where it came from, and are then confused when a file downloaded from a URL lands in the image still compressed. As covered earlier, automatic extraction is specific to local build-context sources, not remote URL sources, and forgetting this distinction leads to real, hard-to-diagnose build output surprises.

✓
Practical Tip

When reviewing an unfamiliar Dockerfile, treat any ADD instruction as a place worth pausing on specifically. Ask what the source actually is — a plain file, an archive, or a URL — because that single detail determines the real, and sometimes surprising, effect that instruction has on the final image.

10Real-world & Industry Examples

The COPY-versus-ADD decision shows up in real Dockerfiles across the industry, often reflecting exactly the trade-offs discussed throughout this article.

Example: Official Language Runtime Images

Many official language runtime image Dockerfiles use COPY almost exclusively for bringing local files into an image, reserving ADD only for the narrow, specific case of extracting a local archive containing a language runtime’s own source distribution during the base image’s own build process — a textbook example of ADD’s extraction behavior used exactly where it earns its keep, and nowhere else.

Example: Web Application Teams and Multi-Stage COPY

A typical web application at a mid-sized technology company builds its frontend assets in one build stage using a full set of build tools, then uses a stage-to-stage COPY to bring only the finished, compiled output into a lightweight final image based on a minimal web server. Neither ADD’s archive extraction nor its URL fetching plays any role here — the entire pattern is built entirely on COPY’s simple, predictable behavior applied cleverly across build stages.

Example: A Cautionary Tale From Public Advisories

Security researchers and container scanning vendors have documented real incidents where a Dockerfile’s reliance on ADD with a remote URL, pointed at a third-party hosting service, resulted in unexpectedly different content being fetched after the original file was moved or the hosting arrangement changed, silently altering images built from an otherwise unchanged Dockerfile. These documented cases are a large part of why official guidance and community linters alike steer engineers away from this specific pattern in production use.

Example: Legacy Codebases Still Using tar Archives

Some long-lived enterprise codebases package portions of their build output as a local tarball for historical reasons predating containerization, and continue to rely on ADD’s automatic local-archive extraction to unpack that tarball cleanly into an image during migration to Docker, rather than rewriting their entire build pipeline at once. This remains one of the clearest, most legitimate real-world uses of ADD’s extra behavior.

COPY
DEFAULT CHOICE ACROSS OFFICIAL IMAGES
ADD
RESERVED FOR LOCAL ARCHIVE EXTRACTION
Rare
LEGITIMATE PRODUCTION USE OF REMOTE ADD

11Frequently Asked Questions

Q1Is ADD simply a more powerful version of COPY that I should always prefer?
No. ADD’s extra behaviors — archive extraction and remote URL fetching — are situational conveniences, not general improvements. Official guidance recommends COPY as the default for its predictability, reserving ADD for the specific cases where its extra behavior is deliberately needed.
Q2Will ADD extract any compressed file I give it, from any source?
No. Automatic extraction only applies to recognized local archive files sitting in the build context. A remote URL pointing at an archive is downloaded as-is, without being automatically unpacked.
Q3Can COPY reach files outside the build context?
No. Both COPY and ADD are restricted to the build context that was sent to the build engine when docker build was run. Neither instruction can pull an arbitrary file from elsewhere on the host machine’s filesystem.
Q4Does using ADD with a URL guarantee I always get the exact same file on every build?
No. Because it performs a live network request at build time, the content returned can change if the remote source changes, which can make builds less reproducible than a COPY instruction referencing a fixed local file.
Q5Can COPY bring files from an earlier build stage in a multi-stage Dockerfile?
Yes. A special form of COPY can reference an earlier build stage as its source, which is a widely used pattern for keeping a final image small while still allowing heavier build tools in an earlier, discarded stage.
Q6Is there ever a genuinely good reason to use ADD with a remote URL?
It is possible, but most style guides and linters recommend against it, preferring an explicit, separate download step with checksum verification, followed by a plain COPY, so the trust boundary stays visible and auditable in the Dockerfile.
Q7Does either instruction affect anything at container run time?
No. Both are build-time instructions only. Whatever files they place into the image are simply already present, baked into a layer, by the time any container starts from that image — neither instruction performs any action while a container is actually running.

12Summary and Key Takeaways

COPY and ADD both move files into a Docker image, but the gap between “simple and predictable” and “powerful but surprising” is exactly what every Dockerfile author needs to keep in mind when choosing between them.

Key Takeaways

  • COPY is a direct, literal file copy. What you specify as the source is exactly what lands in the destination, with no extraction and no network activity.
  • ADD does everything COPY does, plus two extras. It automatically extracts recognized local archives, and can fetch a remote file when given a URL as its source.
  • The extraction behavior is local-only. A URL pointing at an archive is downloaded as-is, without automatic unpacking — a frequently missed asymmetry.
  • Predictability is why COPY is the recommended default. A Dockerfile that relies mainly on COPY is easier for any future reader to understand at a glance.
  • Remote ADD introduces real risk. A live network fetch at build time trusts an external server’s content, with no built-in verification of what comes back.
  • ADD still has a legitimate niche. Extracting a local, version-controlled archive in a single instruction remains a reasonable, well-understood use case.
  • Both are strictly build-time instructions. Neither one does anything once a container built from the resulting image is actually running.