이 문제를 다룬 거의 모든 글은 볼륨을 탓합니다. 수정이 이렇게 오래 걸리는 이유도 바로 그것입니다. 작동하던 bind 마운트가 있었고, 회사 VPN이 연결되었고, 그리고 docker compose up 이제 소스 경로 누락, 빈 디렉터리, 또는 엔진이 만들기를 거부하는 마운트로 실패합니다. 제가 정리한 환경들에서는 Compose 파일이 바뀌지 않았고 경로도 정확했습니다. 달라진 것은 어느 데몬이 요청을 받았는지였고, 마운트 오류는 그 차이가 처음 드러난 지점일 뿐이었습니다.

볼륨은 움직이지 않았습니다. 움직인 것은 요청입니다

어떤 -v /srv/app:/app 플래그는 셸이 수행하는 디렉터리 복사가 아닙니다. 클라이언트가 이를 API 요청으로 바꾸고, 응답하는 데몬이 /srv/app 을 자기 파일 시스템 기준으로 해석합니다. 양쪽이 같은 커널에서 실행되면 그 경로는 파일 관리자에서 보이는 경로와 같습니다. endpoint가 바뀌면 같은 문자열이 프로젝트 디렉터리를 들어본 적 없는 머신에서 조회되고, 데몬은 소스가 없다고 보고하거나 그 자리에 조용히 빈 디렉터리를 만듭니다.

이름 있는 볼륨은 계속 작동하는데 bind 마운트는 그렇지 않은 이유도 같습니다. -v appdata:/app 은 데몬이 소유한 저장소를 지목하므로 호스트 경로가 특정 위치에 존재할 필요가 없습니다. 한 서비스를 bind 마운트에서 이름 있는 볼륨으로 바꿨더니 오류가 사라졌다면 문제를 고친 것이 아닙니다. 클라이언트와 데몬이 그 경로가 어느 파일 시스템에 속하는지에 대해 합의하지 못한다는 것을 확인했을 뿐이고, 그 불일치는 다시 실제 호스트 디렉터리가 필요해지는 순간 돌아옵니다.

마운트를 건드리기 전에 endpoint를 출력하세요

여러 설정이 클라이언트를 endpoint로 향하게 할 수 있고, 그것들은 항상 일치하지 않습니다. --context 플래그, DOCKER_CONTEXT, DOCKER_HOST 현재 셸, 그리고 클라이언트 구성에 저장된 컨텍스트입니다. 우선순위를 따지는 대신 모두 출력하고 답을 비교하세요.

docker context show
docker context ls
docker version
docker info --format "{{.Name}} {{.OperatingSystem}} {{.ServerVersion}}"

클라이언트 구성은 ~/.docker/config.json 에서는 Linux와 macOS에, %USERPROFILE%\.docker\config.json 은 Windows에 있으며, 현재 터미널에서 export한 값은 그 세션에 한해 저장된 값을 덮어씁니다. endpoint가 로컬 소켓이 아니라 호스트 이름이라면 이름 해석과 경로가 어디로 갈지를 결정하며, VPN 클라이언트가 다시 쓰는 것이 바로 그것입니다. 경로를 추가하거나 리졸버를 바꾸는 터널은 예전에 이 노트북을 가리키던 이름을 사내 네트워크 내부 주소로 보낼 수 있습니다.

구성이 아니라 상태를 비교하세요. 터널을 내린 상태에서 docker ps -a 와 docker images 를 실행하고, 그다음 터널을 올린 상태에서 다시 실행하세요. 컨테이너 목록이 다르다는 것은 어떤 구성 파일보다 강한 증거입니다. 다른 데몬이 요청에 응답했다는 뜻이기 때문입니다.

그 경로가 그곳에 있는지 데몬에 물어보세요

어느 데몬에 도달했는지 알았다면, 그 데몬이 디렉터리를 보게 하세요. --mount 형식은 짧은 플래그보다 명확한 메시지로 실패합니다. 컨테이너를 시작하기 전에 소스를 검증하기 때문입니다.

docker run --rm --mount type=bind,source=/srv/app,target=/app alpine ls -la /app

소스가 없다고 보고하면 응답한 머신에서 그것을 만들거나, 서비스를 이름 있는 볼륨으로 돌리고 데이터를 그곳에 복사하세요. 이 확인 전에 볼륨을 다시 만들고 엔진을 재설치해서는 안 됩니다. 그런 행동은 환경을 충분히 바꿔 불일치를 숨기면서 불일치는 그대로 남깁니다.

또 하나의 경로는 전혀 다른 곳에서 실행됩니다. image를 당기는 것은 데몬의 일이지 클라이언트의 일이 아닙니다. 성공적인 풀은 데몬이 registry에 도달할 수 있다는 것만 증명하고, 작동하는 bind 마운트는 경로가 해석되었다는 것만 증명합니다. 각 경로를 실제로 실행되는 곳에서 시험하지 않으면 잘못된 쪽에 오후를 낭비하게 됩니다.

인트라넷 이름은 다른 이유로 실패합니다

같은 보고서의 나머지 절반은 보통 컨테이너가 intranet.corp.example에 도달하지 못한다는 것입니다. 그것은 별개의 메커니즘이지 두 번째 볼륨 문제가 아닙니다. 컨테이너는 시작할 때 /etc/resolv.conf 을 받는데, 이는 데몬 리졸버 설정에서 생성되므로 컨테이너를 만든 뒤 VPN이 추가한 DNS 서버는 컨테이너를 다시 만들기 전까지 그 안에 나타나지 않습니다.

사용자 정의 네트워크에서 Docker의 내장 리졸버는 외부 조회를 호스트에 설정된 서버로 전달합니다. 이는 VPN이 호스트 리졸버를 다시 쓸 때 도움이 되고, 분할 DNS가 내부 영역을 데몬이 더는 보지 못하는 서버로 보낼 때 해가 됩니다. 좁고 명시적인 매핑은 절대 바뀌지 않는 이름에 대한 논쟁을 피합니다.

services:
  web:
    extra_hosts:
      - "intranet.corp.example:10.20.30.40"

이것은 컨테이너의 hosts 파일에 항목을 기록하므로 컨테이너는 터널에 대해 아무것도 배우지 않고, 주소도 고정됩니다. 내부 호스트가 옮겨가면 누군가 Compose 파일을 편집할 때까지 항목은 잘못된 채로 남습니다. 고정된 장비나 고정된 데이터베이스에 사용하고, 움직이는 이름은 DNS에 맡기세요.

재설치가 실제로 바꾸는 것

Docker Desktop을 다시 설치하면 VM, 소켓, 컨텍스트 목록이 다시 만들어지고 마운트가 다시 작동합니다. 그 결과는 실제이지만 아무것도 가르쳐 주지 않으며, 그래서 다음 VPN 업데이트 후에 같은 고장이 돌아옵니다. 재설치는 클라이언트가 쓰던 endpoint를 바꿨을 뿐, 프로젝트가 지목하는 경로와 데몬이 실제로 읽는 파일 시스템을 맞춰 주지 않았습니다.

제 작업 순서는 짧습니다. 클라이언트가 어느 endpoint를 쓸지 확인하세요. 컨테이너 목록을 비교해 어느 데몬이 응답했는지 확인하세요. 그 데몬에 소스 경로를 나열하게 하세요. 그런 다음에야 마운트를 편집하고, hosts 항목을 추가하고, 볼륨 유형을 바꾸세요. VPN으로 일하는 팀을 위해 Compose 파일을 유지한다면, 데몬이 소유해야 할 데이터에는 이름 있는 볼륨을 선호해 호스트 경로가 계약의 일부가 되지 않게 하세요.

관련 글