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

빌드노트

Dockerfile을 바꾸니 같은 빌드 컨텍스트의 파일이 COPY에서 빠진다

같은 빌드 컨텍스트에서도 선택한 Dockerfile의 전용 ignore가 있으면 루트 .dockerignore의 제외 목록은 합쳐지지 않는다. 합성 파일의 포함 목록과 COPY 성공·실패를 비교해 실제 적용 파일을 찾고 필요한 경로를 맞춘다.

빌드 명령의 마지막 경로는 그대로인데, -f로 Dockerfile만 바꿨더니 COPY 결과에서 파일이 사라질 수 있다. 이때는 선택한 Dockerfile 옆의 전용 ignore 파일을 확인한다. 전용 파일이 있으면 컨텍스트 루트의 .dockerignore보다 우선한다. 루트 규칙을 공통 기본값으로 두고 전용 규칙이 거기에 덧붙는다고 생각하면 포함 목록을 잘못 예상하게 된다.

예를 들어 -f docker/build.Dockerfile .로 빌드한다면, 확인할 전용 파일은 docker/build.Dockerfile.dockerignore다. Dockerfile과 같은 디렉터리에 두고 Dockerfile의 전체 이름 뒤에 .dockerignore를 붙인다. docker/.dockerignore를 두는 것과는 다르다. 이 위치와 우선순위는 Docker 공식 문서 (새 탭에서 열림)에 명시돼 있다.

기존 프로젝트를 진단한다면 실제 빌드 명령의 -f와 마지막 경로를 먼저 확인한다. 위 명령을 프로젝트 루트에서 실행했다면, PowerShell에서 다음 두 파일의 제외 규칙을 대조한다.

Get-Content -LiteralPath .dockerignore
Get-Content -LiteralPath docker/build.Dockerfile.dockerignore

전용 파일이 없고 루트 파일만 있다면 루트 규칙을 확인한다. 전용 파일이 있다면 그 내용을 먼저 고쳐야 한다. 루트 파일에서 필요한 경로를 허용해도, 적용되는 전용 파일이 그 경로를 제외하고 있으면 해결되지 않는다. 전용 파일 안의 패턴도 Dockerfile의 디렉터리가 아니라 컨텍스트 루트 기준으로 읽는다.

같은 파일 세 개로 포함 목록을 비교했다

별도 합성 폴더에서 다음 구조를 만들었다. 실제 앱이나 비밀 파일은 넣지 않았다. 직접 비교하려면 Docker CLI·Buildx와 실행 중인 Linux 엔진이 필요하다. 아래 트리의 최상위 폴더를 현재 디렉터리이자 컨텍스트로 사용한다.

.
├── .dockerignore
├── docker/
│   ├── build.Dockerfile
│   ├── build.Dockerfile.dockerignore
│   └── test.Dockerfile
└── payload/
    ├── common.txt
    ├── root-only.txt
    └── special-only.txt

텍스트 파일에는 각각 common, root-only, special-only 한 줄을 넣는다. 루트 .dockerignore에는 payload/root-only.txt 한 줄, 전용 docker/build.Dockerfile.dockerignore에는 payload/special-only.txt 한 줄을 넣는다. test.Dockerfile.dockerignore는 만들지 않는다.

두 Dockerfile은 다음 두 줄로 같게 만든다. 이 복사 결과로 payload에서 어떤 파일이 제외됐는지 확인한다.

FROM scratch
COPY payload/ /payload/

다음 명령은 각각의 빌드 결과를 호스트 디렉터리로 내보내고, 복사된 파일 이름을 조회한다. out-root와 out-specific은 컨텍스트 한 단계 위의 아직 존재하지 않는 새 경로여야 한다. 기존 결과가 섞이면 제외된 파일도 남아 있는 것처럼 보일 수 있다. --output type=local은 빌드 결과 파일을 내보내는 옵션이며, 이 예제는 실행할 컨테이너를 만들지 않는다. Buildx 출력 옵션 (새 탭에서 열림)

docker buildx build --no-cache --progress=plain -f docker/test.Dockerfile --output type=local,dest=../out-root .
Get-ChildItem -Name ../out-root/payload
docker buildx build --no-cache --progress=plain -f docker/build.Dockerfile --output type=local,dest=../out-specific .
Get-ChildItem -Name ../out-specific/payload

직접 실행한 두 빌드는 모두 종료 코드 0이었다. 파일 목록은 다음처럼 달랐다.

  • test.Dockerfile: common.txt, special-only.txt. 대응하는 전용 파일이 없으므로 루트 규칙대로 root-only.txt가 빠졌다.
  • build.Dockerfile: common.txt, root-only.txt. 전용 규칙대로 special-only.txt가 빠졌고, 루트에서 제외했던 root-only.txt는 다시 들어왔다.

루트에서만 제외한 파일이 전용 규칙을 쓰는 빌드에 들어온 것이 핵심이다. 이 결과는 두 ignore 파일의 제외 목록을 합친 결과와 다르다. 같은 build.Dockerfile에 전용 파일을 추가하기 전후도 따로 비교했다. 추가 전에는 common.txt와 special-only.txt, 추가 후에는 common.txt와 root-only.txt가 복사돼 같은 차이를 확인했다.

디렉터리 COPY는 성공해도 파일 하나를 지정하면 실패한다

위 디렉터리 복사는 남은 파일만으로 성공했다. 이번에는 docker/build.Dockerfile을 다음처럼 바꿔, 전용 규칙이 제외하는 파일을 직접 요구한다. ignore 파일 두 개는 그대로 둔다.

FROM scratch
COPY payload/special-only.txt /special-only.txt

새 출력 경로 out-required를 지정해 이 파일을 복사할 수 있는지 확인한다.

docker buildx build --no-cache --progress=plain -f docker/build.Dockerfile --output type=local,dest=../out-required .

이 빌드는 종료 코드 1로 끝났다. 다음은 실제 오류에서 빌드마다 달라지는 checksum 참조 식별자를 뺀 부분이다.

"/payload/special-only.txt": not found

같은 실행에서는 CopyIgnoredFile 경고도 나왔다. 호스트에는 파일이 있고 마지막 컨텍스트 경로도 같지만, 적용된 전용 ignore가 복사 입력에서 파일을 제외한 것이다. 이 오류가 나오면 컨텍스트 경로를 넓히기 전에 어떤 ignore 파일을 읽었는지 확인할 이유가 있다.

이 예제에서는 docker/build.Dockerfile.dockerignore의 내용을 payload/root-only.txt 한 줄로 바꿨다. 필요한 special-only.txt를 제외하지 않으면서, 원래 루트에서 제외하려던 파일은 전용 규칙에도 명시한 것이다. 실패한 위 명령을 다시 실행하자 종료 코드 0이었고, out-required/special-only.txt의 내용은 원본과 같은 special-only였다. 디렉터리 복사로도 다시 확인해 common.txt와 special-only.txt만 남았다.

실제 프로젝트에서는 전용 파일을 통째로 비우기보다 필요한 경로를 제외하는 규칙을 좁힌다. 모든 Dockerfile에서 빼야 하는 경로가 있다면 각 전용 파일에도 그 제외 규칙이 들어 있는지 확인한다. 루트에 적어 두었다는 이유만으로 모든 빌드에 적용된다고 가정하지 않는다.

2026년 10월 22일 Windows 11에서 Docker Desktop 4.92.0, Linux Engine 29.8.0, Buildx 0.37.1, BuildKit 0.33.0으로 확인했다. 합성 파일과 FROM scratch를 사용한 로컬 디렉터리 컨텍스트의 비교다. 목록은 COPY payload/로 복사 가능한 파일을 보여 주며, 빌드 정의를 위한 모든 전송 파일을 열거한 것은 아니다. Compose, 원격·이름 있는 컨텍스트, 표준 입력 Dockerfile, 구형 빌더에서 같은 비교는 실행하지 않았다.

← 전체 글 보기