Introduction
Docker 25.0 introduced a powerful but often overlooked enhancement: the --link
flag for the COPY
command. This feature is available only when using BuildKit, Docker’s modern build engine, and it can significantly boost your build performance and reduce disk usage—especially in complex projects.
What Is COPY --link
?
Normally, COPY
creates new copies of the files—one per layer, even if the files are identical. That means wasted space and slower builds.
When you use COPY --link
, Docker preserves hard links between identical files. That way, multiple references to the same file don't occupy extra space in your image. It’s essentially a space-saving technique that makes builds more efficient when file deduplication is possible.
COPY --link
tells Docker:
"Copy this folder as its own independent layer. If the source files didn't change, reuse the layer even if earlier steps did."
This creates better cache separation, helping you:
Skip unnecessary rebuilds
Speed up CI/CD pipelines
Push smaller diffs to registries
More info:
hard links - a feature of file systems that allow multiple directory entries to refer to the same physical file on a disk.
Read more about COPY --link in Docker Docs
References.
When Should You Use It?
Use COPY --link
in these situations:
Large Shared Libraries
You have common libraries or packages reused across multiple containers or build stages. This avoids duplicating them physically.Monorepo Structures
You copy the same modules or folders in differentCOPY
steps. This prevents Docker from storing multiple identical copies.Multi-stage Builds
You're copying files from a previous stage into the final image. If the files already exist and haven’t changed,--link
helps save space.Node Modules / Vendor Folders
If youCOPY
these folders for multiple microservices and they share a common dependency tree,--link
reduces redundant data.
When Not to Use It
While COPY --link
can save space and speed up builds, there are some cases where it's better not to use it:
1. You Plan to Modify the Files Later
Hard links point to the same underlying file data. If you modify a file after copying it with --link
, the hard link is broken and a full copy is created anyway.
This defeats the purpose and can lead to unexpected behavior or inconsistent builds.
2. You're Working on a Filesystem That Doesn’t Support Hard Links
Some filesystems — especially remote or mounted volumes like:
NFS (Network File System)
SMB (Samba/Windows shares)
Some Docker volumes or virtual filesystems
do not support hard linking.
If --link
is used in these environments, the build may fail or silently skip the optimization.
3. You Require Full File Isolation for Security or Compliance
In environments where each layer must have completely isolated copies of files (e.g., strict audit/compliance pipelines or sandboxed builds):
🔐 Using hard links might be seen as a risk, since files technically reference the same inode on disk.
4. You're Building Images for Distribution Across Different OSes or CI/CD Pipelines
Hard links might behave differently across:
Linux vs macOS
Local vs cloud build runners
CI environments with containerized build agents
💡 Inconsistent behavior might cause subtle bugs, so using the standard COPY
is often safer for portability.
5.You're Building Small Front-End Applications
For lightweight front-end apps (like static Next.js, React, or Angular builds), the benefits of --link
are often negligible.
⚙️ These builds are typically fast and have small file sets, so the complexity of managing hard links isn’t worth the minimal gain. In fact, it can add more confusion than value.
Example of using Copy --link in Angular Application
🧑💻 Git repo
Official Docker angular Sample repo written by me.
https://github.com/kristiyan-velkov/docker-angular-sample/tree/develop
Docker file:
# =========================================
# Stage 1: Build the Angular Application
# =========================================
ARG NODE_VERSION=22.14.0-alpine
ARG NGINX_VERSION=alpine3.21
# Use a lightweight Node.js image for building (customizable via ARG)
FROM node:${NODE_VERSION} AS builder
# Set the working directory inside the container
WORKDIR /app
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json package-lock.json ./
# Install project dependencies using npm ci (ensures a clean, reproducible install)
RUN --mount=type=cache,target=/root/.npm npm ci
# Copy the rest of the application source code into the container
COPY . .
# Build the Angular application
RUN npm run build
# =========================================
# Stage 2: Prepare Nginx to Serve Static Files
# =========================================
FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner
# Use a built-in non-root user for security best practices
USER nginx
# Copy custom Nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy the static build output from the build stage to Nginx's default HTML serving directory
COPY --link --from=builder /app/dist/*/browser /usr/share/nginx/html
# Example without --link
# COPY --chown=nginx:nginx --from=builder /app/dist/*/browser /usr/share/nginx/html
# Expose port 8080 to allow HTTP traffic
# Note: The default NGINX container now listens on port 8080 instead of 80
EXPOSE 8080
# Start Nginx directly with custom config
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]
What Will --link
Do Here?
In your line:
COPY --link --chown=nginx:nginx --from=builder /app/dist/*/browser /usr/share/nginx/html
the --link
flag tells Docker:
“Copy the Angular build files into the Nginx image, but treat this copy as its own independent layer. If these files haven’t changed, reuse this layer — even if earlier steps did change.”
So, if you later change:
The Nginx base image version
The
nginx.conf
fileAny config at the end of the runner stage
Docker will skip re-copying the Angular build into the runner image if the output of npm run build
hasn’t changed.
What Problems Can Occur?
Using --link
has some important restrictions:
No access to previous layer files
The destination directory (/usr/share/nginx/html
) must not rely on files or symlinks from earlier steps. The--link
layer is isolated — it can't read or depend on what was already there.Symlinks in target path won’t work
If/usr/share/nginx/html
had symlinks from previous steps, they won’t be followed or preserved.Cannot mix with layers expecting continuity
If something in your image expects thehtml
folder to already contain files or a specific structure from a previous step — it will break.
What About File Ownership with COPY --link?
By default, `COPY --link` preserves the original file ownership. In most Node.js builder stages, that means files are owned by `root`. Since the Nginx container runs as the `nginx` user, it only needs read access, which it still has.
Your Build Times
With --link
docker build -t angular-link . # Result: 2.419s total
Without --link (
Uncomment the example without link and comment the —link one)
docker build -t angular-no-link . # Result: 1.518s total
🤨 Wait... Isn't --link
Supposed to Be Faster?
Yes — but here’s why in your case the no-link version seems faster on a clean build:
On First-Time Builds:
Docker has no cache yet.
--link
adds a small setup overhead to isolate the layer.That extra logic makes the first build with
--link
look slightly slower.
Where --link
Really Shines
Try this simple test to see the difference:
Add this line after the
COPY
command in your Dockerfile to simulate a change:
# Simulate a later change
ENV TEST_REBUILD=1
Rebuild both versions:
With
--link
:
docker build -t angular-link . # Time: 0.32s
Without
--link
:
docker build -t angular-link . # Example: 0.33s
What Happens
With
--link
: Docker skips recopying the Angular build output — it uses the cached layer directly.Without
--link
: Docker recopies everything from the builder stage, even though nothing changed in the output.
This is where --link
shows its true power — smarter caching and faster rebuilds when only later layers are modified.
--link
isn’t about speed on first builds — it’s about smart caching and avoiding unnecessary work later.
Final Thoughts
COPY --link
is great when it fits the use case:
Really Heavy front-end builds (Angular, React, Vue)
Multi-stage Dockerfiles
CI systems with caching and long pipelines
But for small projects or local dev builds?
I personally suggest to stick with standard COPY
.
Use --link
intentionally. Measure the results.
And choose what gives your team the real speed-up.
❤️ If you like my work, please follow me and subscribe ❤️
Front-end World is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.
Share this post