빌드노트
docker cp 뒤 dist 폴더가 한 단계 더 생겼다면
docker cp에서 소스 폴더와 폴더/.는 기존 목적지에 서로 다른 트리를 만든다. 기존·신규 목적지의 실제 복사와 해시 비교로 차이를 확인하고, 같은 명령을 다시 실행할 때 하위 폴더가 생기는 이유와 복사 전 확인할 조건을 설명한다.
docker cp ./dist 컨테이너:/srv/www로 파일을 보냈는데, 기대한 /srv/www/index.html 대신 /srv/www/dist/index.html이 생길 수 있다. 목적지가 이미 디렉터리였다면 ./dist는 그 안에 폴더째 들어간다. 내부 파일을 바로 넣으려면 소스를 ./dist/.로 쓴다. 복사 전 목적지가 없었는지도 함께 확인해야 한다. 같은 명령도 처음 실행할 때와 다시 실행할 때의 배치가 달라질 수 있다.
방금 복사한 목적지에서 한 단계 더 들어갔는지 본다
먼저 목적지가 디렉터리인지, 복사된 파일이 어느 깊이에 있는지 확인한다. 다음은 PowerShell에서 쓰는 조회다. Docker CLI와 실행 중인 Linux 컨테이너가 필요하며, 컨테이너 안에 sh와 find가 있어야 한다. my-container는 복사에 쓴 컨테이너 이름으로, /lab/existing-dir는 자신의 목적지 경로로 바꾼다. 아래 출력은 별도 실습의 경로를 그대로 쓴 것이다.
$c = 'my-container'
docker exec $c sh -c 'if [ -d /lab/existing-dir ]; then echo directory; elif [ -e /lab/existing-dir ]; then echo not-directory; else echo missing; fi'
docker exec $c find /lab/existing-dir -maxdepth 3 -type f
이미 있던 빈 목적지에 ./dist를 복사한 실습에서는 다음처럼 나왔다. 파일 순서는 정렬해 표시했다.
directory
/lab/existing-dir/dist/.build-id
/lab/existing-dir/dist/assets/app.js
/lab/existing-dir/dist/index.html
목적지 바로 아래에 있어야 할 파일 앞에 dist/가 붙었다. 이 경우 복사 성공 메시지만으로는 원하는 경로에 도착했는지 알 수 없다. 첫 명령의 directory는 조회 시점의 상태다. 복사가 목적지를 새로 만들었을 수도 있으므로, 이 출력만으로 복사 전부터 존재했다고 단정하지 않는다. not-directory면 파일 등 다른 종류의 목적지인지 확인한다. missing이면 뒤의 find도 실패할 수 있다. 셸이나 컨테이너 실행 오류는 이 세 상태를 확인한 결과가 아니다.
목적지가 없을 때는 두 소스가 같은 모양을 만들었다
Docker의 docker cp 문서 (새 탭에서 열림)는 소스가 디렉터리일 때 목적지의 존재 여부와 소스 끝의 /.를 구분한다. 목적지가 없으면 그 이름으로 디렉터리를 만들고 내용을 넣는다. 이미 디렉터리라면 /.가 없는 소스는 폴더째, /.가 있는 소스는 내부 내용을 넣는다. 끝에 /만 붙인 것과 /.는 다르다.
이 차이를 확인하려고 합성 dist 폴더에 index.html, assets/app.js, .build-id 세 파일을 만들었다. 새 실습 컨테이너 안에서 아래 네 목적지를 서로 따로 두고 복사했다. 기존 목적지는 빈 디렉터리였고, 새 목적지의 부모인 /lab은 미리 존재했다.
./dist→ 기존/lab/existing-dir:index.html이/lab/existing-dir/dist/index.html에 생겼다../dist/.→ 기존/lab/existing-dot:/lab/existing-dot/index.html에 생겼다../dist→ 없던/lab/new-dir:/lab/new-dir/index.html에 생겼다../dist/.→ 없던/lab/new-dot:/lab/new-dot/index.html에 생겼다.
네 경우 모두 세 파일의 SHA-256이 원본과 같았다. 내용이 손상된 문제가 아니라 내용을 담는 디렉터리의 위치가 달라진 것이다. 추가로 ./dist/를 기존 목적지에 복사해도 dist가 한 단계 생겼다. 슬래시 하나만 덧붙여서는 내부 내용 복사로 바뀌지 않았다.
특히 세 번째 복사를 같은 소스·목적지로 다시 실행했을 때 차이가 드러났다. 첫 실행에서는 없던 목적지가 두 번째 실행 때는 이미 디렉터리였다. find로 확인한 트리에는 다음 두 파일이 함께 있었다.
/lab/new-dir/index.html
/lab/new-dir/dist/index.html
두 파일의 해시도 같았다. 첫 실행이 원하는 모습이었다는 이유만으로 재실행도 같은 위치를 갱신한다고 볼 수는 없다. 복사 스크립트에서는 소스 표기와 함께 복사 직전 목적지 상태를 조건으로 다뤄야 한다.
내부 내용을 넣으려면 기존 목적지와 /.를 함께 명시한다
현재 폴더에 보낼 dist 디렉터리가 있고, 목적지 바로 아래에 그 내용이 있어야 한다면 다음처럼 복사한다. 앞서 설정한 $c를 사용하며, 경로는 조회 때와 같은 실제 목적지로 바꾼다. 첫 검사가 실패하면 복사하지 않고 중단한다.
docker exec $c test -d /lab/existing-dir
if ($LASTEXITCODE -ne 0) { throw 'destination is not a directory' }
docker cp './dist/.' "${c}:/lab/existing-dir"
if ($LASTEXITCODE -ne 0) { throw 'copy failed' }
docker exec $c find /lab/existing-dir -maxdepth 3 -type f
실습에서 이 명령을 실행하자 목적지 바로 아래에 .build-id, assets/app.js, index.html이 생겼다. 앞서 잘못 배치한 dist/ 아래의 세 파일도 남아 있었다. /.로 다시 복사했다고 기존 하위 폴더까지 정리되지는 않았다. 실제로 읽는 경로에 파일이 생겼는지 확인하고, 예전에 생긴 폴더의 삭제 여부는 별도로 판단한다.
반대로 목적지 안에 dist 폴더 자체를 두려는 경우라면, 목적지가 이미 디렉터리인지 확인한 뒤 ./dist를 소스로 쓴다. 목적지가 없을 때 이름을 대신 만들게 두면 같은 명령을 반복할 때의 의미가 달라진다.
목적지가 없는 경우에도 부모 경로는 있어야 한다. 이번에 /lab/missing-parent/target으로 복사했을 때는 종료 코드 1과 부모 /lab/missing-parent를 찾을 수 없다는 오류가 났고, 부모도 생성되지 않았다. 부모까지 자동으로 만드는 명령으로 생각하지 않는다. 필요한 경로를 먼저 준비하고 다시 확인한다.
2026년 10월 21일 KST에 Windows 11·PowerShell 7.6.6·Docker Desktop 4.92.0의 Linux Engine 29.8.0과 BusyBox 1.37.0에서 확인했다. 위 PowerShell 조회·수정도 같은 합성 파일과 실습 컨테이너로 실행했다. 컨테이너에서 호스트로 꺼내는 방향도 기존·신규 목적지와 /. 조합 네 가지를 비교해 같은 배치 규칙과 파일 해시를 확인했다. 심볼릭 링크, 권한·소유권 보존, 마운트된 경로, Windows 컨테이너는 검증하지 않았다. 이 관찰은 일반 디렉터리의 복사 위치에 관한 것이며, 복사된 파일을 실제 서비스가 읽는지까지 확인한 결과는 아니다.