빌드노트
이전 단계에 파일이 있는데 COPY --from이 못 찾는 이유
이전 빌드 단계의 WORKDIR에서 만든 파일을 COPY --from이 못 찾을 때, 소스 단계의 루트와 현재 단계의 상대 목적지를 구별한다. 소스 경로만 바꾼 실패·성공 빌드와 최종 파일 11바이트를 직접 비교했다.
멀티스테이지 Docker 빌드에서 build 단계의 파일 생성은 성공했는데, 다음 단계의 COPY --from=build dist/message.txt ./가 not found로 끝날 수 있다. COPY --from의 소스는 이전 단계의 WORKDIR가 아니라 그 단계의 파일시스템 루트에서 찾는다. /workspace에서 만든 dist/message.txt라면 소스에 /workspace/dist/message.txt를 적어야 한다. Dockerfile의 COPY --from 규칙 (새 탭에서 열림)
파일을 만든 단계의 위치부터 확인한다
자신의 Dockerfile에서는 --from=build가 가리키는 단계의 파일 생성 명령 뒤에 RUN pwd && ls -l dist/message.txt를 잠시 추가한다. 파일명은 자신의 산출물에 맞춘다. pwd는 생성 단계의 작업 폴더를, ls는 그 위치에서 파일이 실제로 보이는지를 확인한다. 뒤에 파일을 옮기거나 지우는 명령이 있다면 그 작업 뒤에도 확인해야 한다.
다음은 경로 차이만 확인하도록 새로 만든 두 단계 예제다. 실행 중인 Linux Docker 엔진과 Docker CLI가 필요하며, busybox:1.37.0 이미지를 사용할 수 있어야 한다. 빈 폴더에 아래 내용을 Dockerfile.bad로 저장한다. 호스트에 dist 폴더를 준비할 필요는 없다.
FROM busybox:1.37.0 AS build
WORKDIR /workspace
RUN mkdir -p dist && printf 'from-build\n' > dist/message.txt
RUN pwd && ls -l dist/message.txt
FROM busybox:1.37.0 AS final
WORKDIR /runtime
COPY --from=build dist/message.txt ./
첫 단계의 WORKDIR /workspace 때문에 RUN이 만드는 파일의 전체 경로는 /workspace/dist/message.txt다. 두 번째 FROM은 새 단계를 시작한다. 그곳의 WORKDIR /runtime이 이전 단계 파일의 위치를 바꾸지는 않는다. 멀티스테이지 빌드와 단계 이름 (새 탭에서 열림)
파일을 저장한 폴더에서 다음 명령을 실행하면, 파일 조회와 실패한 복사 경로를 함께 볼 수 있다. 명령은 PowerShell에서 확인했다. 태그에는 다른 이미지와 겹치지 않는 실습용 이름을 쓴다. --progress=plain은 조회 출력을 읽기 위한 옵션이고, --no-cache는 조회 RUN을 다시 실행하기 위한 옵션이다. 빌드 옵션 (새 탭에서 열림)
docker build --progress=plain --no-cache -f Dockerfile.bad -t copyroot-20261022-0350:bad .
직접 실행한 빌드에서 pwd는 /workspace를 출력했고, ls는 dist/message.txt가 11바이트 파일임을 보여 줬다. 그런데 복사는 종료 코드 1로 실패했다. 오류의 빌드별 식별자를 제외하면 핵심은 다음 문자열이다.
"/dist/message.txt": not found
이 문자열은 빌더가 소스 단계의 /dist/message.txt를 찾았다는 단서다. 파일이 있는 /workspace/dist/message.txt와 경로가 다르다. 이 예제의 파일은 build 단계의 RUN이 만들었으므로, 호스트 폴더나 .dockerignore를 바꿔도 잘못 적힌 단계 내부 소스 경로는 고쳐지지 않는다.
소스에 /workspace를 포함하고 목적지는 그대로 둔다
Dockerfile.bad를 Dockerfile.good라는 이름으로 복사하고 마지막 줄만 다음처럼 바꾼다. 바꿀 곳은 --from=build 뒤의 소스 경로다.
COPY --from=build /workspace/dist/message.txt ./
현재 단계는 WORKDIR /runtime이므로 상대 목적지 ./는 /runtime/을 뜻한다. 따라서 파일은 최종 이미지의 /runtime/message.txt에 놓인다. 소스는 build 단계의 루트에서, 상대 목적지는 현재 단계의 작업 폴더에서 읽는다. COPY 목적지 규칙 (새 탭에서 열림)
같은 폴더에서 수정한 파일을 빌드하고, 성공한 경우에만 두 번째 명령으로 최종 파일을 읽는다.
docker build --progress=plain --no-cache -f Dockerfile.good -t copyroot-20261022-0350:good .
docker run --rm --network=none copyroot-20261022-0350:good cat /runtime/message.txt
수정한 빌드와 파일 조회는 모두 종료 코드 0으로 끝났고, 조회 결과는 from-build 한 줄이었다. 화면 출력과 별도로 확인한 최종 파일의 원시 바이트도 처음 만든 내용과 같은 11바이트였다. 마지막 바이트는 줄바꿈 0a였으며, 복사하면서 파일 내용이 달라지지 않았다.
소스 앞에 /만 붙이는 것으로는 부족하다. 별도 비교에서 /dist/message.txt도 같은 오류로 실패했다. 반대로 workspace/dist/message.txt는 앞의 / 없이도 성공했다. 상대 표기 역시 소스 단계의 루트에서 해석되기 때문이다. 수정할 때는 기준이 눈에 드러나도록 /workspace/dist/message.txt처럼 실제 전체 경로를 쓰면 된다.
자신의 파일 조회부터 실패한다면 생성 위치나 파일명을 먼저 바로잡는다. 조회는 성공했는데 복사가 실패한다면, 오류에 나온 경로와 생성 단계에 남아 있는 파일의 전체 경로를 대조한다. 현재 단계의 WORKDIR를 옮겨 소스 경로를 맞추려 할 필요는 없다.
이 비교는 2026년 10월 22일 KST에 Windows 11의 Docker Desktop 4.92.0, Linux Engine 29.8.0, BuildKit 0.33.0, Buildx 0.37.1과 busybox:1.37.0으로 실행했다. 확인한 범위는 로컬 Linux 두 단계 빌드의 일반 파일 복사다. Windows 컨테이너, 외부 이미지에서의 복사, 별도로 지정한 named context는 실행하지 않았다.