본문 바로가기
빌드노트 검색

빌드노트

Docker COPY가 config.txt를 /sample이라는 파일로 만들었다

Docker COPY가 단일 파일을 존재하지 않는 /sample에 복사하면 /sample 자체가 파일이 될 수 있다. 목적지 끝의 /와 기존 디렉터리 유무를 세 조건으로 비교하고, 이미지에서 경로 종류를 조회해 복사 의도를 명확히 하는 방법을 설명한다.

Dockerfile에 COPY config.txt /sample을 쓰고 빌드도 성공했는데, 이미지 안에서 /sample/config.txt를 찾을 수 없을 때가 있다. 복사 전에 /sample이 없었다면 /sample 자체가 파일이 된다. 원본 이름을 유지해 디렉터리 안에 넣으려면 목적지를 /sample/로 적는다. 단일 파일을 복사할 때는 목적지 끝의 /와 그 경로가 이미 디렉터리였는지를 함께 확인해야 한다.

Dockerfile의 COPY 목적지 설명 (새 탭에서 열림)은 끝 슬래시에 따라 파일이 놓이는 경로가 달라진다고 명시한다. 원본 파일을 못 찾아 빌드가 실패하는 경우와 달리, 이 문제에서는 복사가 성공해도 애플리케이션이 읽는 경로와 결과가 어긋난다.

/sample/config.txt보다 /sample의 종류부터 본다

자신의 Dockerfile에서 문제가 되는 COPY의 목적지를 확인한 뒤, 빌드한 이미지에서 그 경로를 조회한다. 아래는 PowerShell 명령이다. $image를 확인할 이미지 태그로 바꾸고 /sample도 실제 목적지에 맞춘다. 이미지에 /bin/sh와 ls가 있어야 하며, 실행 중인 Linux Docker 엔진이 필요하다.

$image = '확인할-이미지:태그'
docker run --rm --network=none --entrypoint /bin/sh $image -c 'ls -ld /sample /sample/config.txt'

이 명령은 이미지에서 새 컨테이너를 만들어 두 경로의 종류를 읽는다. --entrypoint /bin/sh는 기본 실행 명령 대신 조회용 셸을 시작한다. 컨테이너는 명령이 끝나면 제거된다. docker run 옵션 (새 탭에서 열림)

별도 합성 예제의 문제 조건에서는 /sample 행의 첫 문자가 -였고, 다음 오류도 나왔다. 출력 중 오류 부분만 옮겼다.

ls: /sample/config.txt: Not a directory

-는 일반 파일, d는 디렉터리를 뜻한다. 여기서는 /sample이 파일이어서 그 아래의 config.txt로 내려갈 수 없다. 이 조회는 종료 코드 1로 끝났지만 이미지를 만드는 빌드는 성공했다. 반대로 /sample이 d이고 하위 파일도 보인다면 이 글의 문제 조건과 다르다. 두 경로 모두 없다면 먼저 확인 중인 이미지와 Dockerfile의 목적지가 맞는지 대조한다. 셸 실행 실패나 권한 오류는 경로 종류를 확인한 결과로 해석하지 않는다.

같은 파일에서 목적지 끝의 /만 바꿨다

재현에는 로컬의 합성 config.txt 하나만 사용했다. 직접 비교하려면 Docker CLI와 Linux 엔진, busybox:1.37.0 이미지를 사용할 수 있는 환경을 준비한다. 새 빈 폴더에 UTF-8 일반 텍스트로 mode=lab 한 줄과 줄바꿈을 담은 config.txt를 저장한다. 같은 폴더의 Dockerfile에는 다음 두 줄을 넣는다.

FROM busybox:1.37.0
COPY config.txt /sample

이 폴더에서 다음 명령으로 빌드한다. 태그에는 다른 이미지와 겹치지 않는 실습용 이름을 사용한다. 빌드가 성공하면 앞의 조회 명령에서 $image를 copydest-lab-20261024:plain으로 바꿔 실행한다.

docker build --network=none -t copydest-lab-20261024:plain .

직접 실행한 빌드는 종료 코드 0이었다. test -f /sample은 참, test -d /sample과 test -f /sample/config.txt는 거짓이었다. /sample의 내용을 읽으니 mode=lab이 나왔다. 파일 내용은 복사됐지만 최종 파일 이름이 sample이 된 것이다. 확장자가 없는 이름만으로 디렉터리라고 판단할 수는 없다.

이제 Dockerfile의 두 번째 줄만 다음처럼 바꾼다. /sample/은 config.txt를 담을 디렉터리라는 의도를 드러낸다.

COPY config.txt /sample/

새 태그로 빌드한 뒤, 성공한 경우에만 디렉터리와 하위 파일을 검사하고 내용을 읽는다.

docker build --network=none -t copydest-lab-20261024:slash .
docker run --rm --network=none --entrypoint /bin/sh copydest-lab-20261024:slash -c 'test -d /sample && test -f /sample/config.txt && cat /sample/config.txt'

두 명령 모두 종료 코드 0이었고 출력은 mode=lab 한 줄이었다. 이번에는 /sample이 디렉터리이고 /sample/config.txt가 일반 파일이었다. 처음에는 없던 디렉터리도 복사하면서 생성됐다. 빌드의 --network=none은 RUN의 네트워크를 제한하는 옵션이며, 베이스 이미지를 처음 준비할 때의 다운로드를 막는 옵션은 아니다. 빌드 옵션 설명 (새 탭에서 열림)

슬래시가 없어도 되던 Dockerfile에는 디렉터리가 있었다

끝의 /를 생략한 모든 복사가 파일 이름을 바꾸는 것은 아니다. 앞에서 /sample을 디렉터리로 만든 대조 조건도 실행했다. config.txt는 그대로 두고 Dockerfile만 다음처럼 구성한 경우다.

FROM busybox:1.37.0
RUN mkdir /sample
COPY config.txt /sample

이 빌드도 성공했고, test -d /sample과 test -f /sample/config.txt가 모두 참이었다. /sample/config.txt의 내용도 mode=lab이었다. 같은 COPY config.txt /sample이라도 목적지가 이미 디렉터리이면 그 안에 원본 이름으로 들어갔다. 이 대조는 직접 실행해 확인한 결과다.

따라서 기존 Dockerfile을 고칠 때는 베이스 이미지와 COPY 앞의 명령이 목적지를 만들어 두었는지도 본다. 디렉터리에 넣을 의도라면 COPY config.txt /sample/로 적어 기존 디렉터리의 존재에 기대지 않도록 한다. 최종 파일 경로까지 지정하려면 COPY config.txt /sample/config.txt처럼 파일 이름을 포함한다. 없는 중간 디렉터리의 생성은 공식 문서에서 확인한 규칙이며, 이 마지막 표기는 이번 실행의 별도 비교 대상은 아니다.

이 비교는 2026년 10월 24일 KST에 Windows의 PowerShell 5.1, Docker Desktop 4.92.0, Linux Engine·CLI 29.8.0, BuildKit v0.33.0, Buildx v0.37.1에서 실행했다. 확인 컨테이너는 네트워크 none, 마운트 없는 조건이었다. 단일 일반 파일을 절대 목적지로 복사한 결과이며, Windows 컨테이너·심볼릭 링크 목적지·여러 원본·런타임 마운트가 있는 경로는 검증하지 않았다.

← 전체 글 보기