Dockerfile: COPY --link --from=<stage> / / copies the stage instead of merging its snapshot
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 43/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- docker, dockerfile, go
- Domain
- build-system, devops
Research direction
Start with frontend/dockerfile/dockerfile2llb/convert_copy.go around lines 337–352, which the issue identifies as building the linked copy. Reproduce with BuildKit v0.34.2 and the provided Dockerfile, then inspect how source stages, Merge, and whiteouts are handled. Done means the eligible root-to-root linked copy reuses the stage snapshot without changing layer contents or behavior for cases with deletions.
Written by the indexing model from the issue text.
Description
COPY --link --from=<stage> / / writes a full new copy of the stage to disk.
The stage result is already a snapshot, so BuildKit could merge that snapshot on top of the base with no copy.
We build a matrix of toolchain images on one BuildKit daemon, for example 12 Go versions on 4 Ubuntu releases.
Each toolchain has a scratch stage that holds a trimmed tree, and several images copy that tree with COPY --link.
--link makes the copy independent of the base, so all images share it.
But it is still a second full copy of the scratch stage, and each stage between the download and the final image adds one more copy.
For 12 Go versions, that is about 3GB of extra disk on each builder for each extra stage.
Reproduce
BuildKit v0.34.2, docker-container driver, docker/dockerfile:1.
# syntax=docker/dockerfile:1
FROM scratch AS download
ADD --link --unpack=true --checksum=sha256:65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40 \
https://go.dev/dl/go1.27.3.linux-amd64.tar.gz /
FROM scratch AS golang-binary
COPY --from=download --link --exclude=doc/** --exclude=test/** --exclude=api/** /go /usr/local/go
FROM ubuntu:jammy AS root
COPY --from=golang-binary --link / /
docker buildx build --target root --output type=cacheonly .
docker buildx du --verbose
| Record | Size |
|---|---|
[download 1/1] ADD --link --unpack=true ... |
295.8MB |
[golang-binary 1/1] COPY --from=download --link --exclude=... |
264.9MB |
[root 2/3] COPY --from=golang-binary --link / / |
264.9MB |
[root 2/3] LINK COPY --from=golang-binary --link / / |
5.9MB |
The third record holds the same files as the second.
Cause
With --link, the Dockerfile frontend makes Merge(base, Scratch().File(Copy(src, ...))):
https://github.com/moby/buildkit/blob/v0.34.2/frontend/dockerfile/dockerfile2llb/convert_copy.go#L337-L352
The Copy runs even when the source is the root of another stage and the destination is /.
Proposal
If COPY --link --from=<stage> / / has no --chown, --chmod, --exclude or --parents, make Merge(base, <stage>) and skip the Copy.
The layer content is the same, and the stage is stored once.
A merge can give a different result from a copy when the stage has deleted files (whiteouts).
If that is a problem, the change could apply only to stages that are built from scratch with file operations.
A new Dockerfile flag for this case would also work for us.
- Dominant language
- Go
- Stars
- 10.4k
- Forks
- 1.6k
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 72
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from moby/buildkit
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 2 days
-
docs: rootless.md should recommend a per-binary AppArmor profile over `apparmor_restrict_unprivileged_userns=0Possibly taken @shashankvarma499 claimed this 59 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 2 days
-
area/dockerfile
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 2 days
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Maintainers usually reply within 2 days
-
status/triage
Difficulty 4/5 3-5 days Newbie friendliness 43/100
Maintainers usually reply within 2 days
Similar issues
-
model:sonnet runtime:claude security size:S
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
FootprintAI/Containarium#3671 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 5 days
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
keyxmakerx/Chronicle#2190 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
llm-d/llm-d-inference-sim#897 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
inference-sim/inference-sim#2110 ·
Maintainers usually reply within 1 day