Docker Hub 풀 타임아웃이 Clash 문제처럼 보이는 이유

Docker Hub 풀 타임아웃은 개발 환경에서 자주 발생하지만, 메시지만 보고 곧바로 특정 노드나 구독 문제라고 단정하기는 어렵습니다. docker pull이 멈추거나 Client.Timeout exceeded while awaiting headers, context deadline exceeded, i/o timeout 같은 오류를 내는 경우에는 Docker CLI, Docker daemon, DNS resolver, 호스트 운영체제의 라우팅, Clash의 프록시 적용 범위를 함께 확인해야 합니다. 브라우저에서 Docker Hub 웹 페이지가 열리는 것만으로는 Docker 이미지 다운로드가 정상이라고 판단할 수 없습니다.

브라우저는 운영체제의 시스템 프록시 설정이나 확장 프로그램을 따르는 경우가 많지만, 실제 이미지 다운로드를 수행하는 프로세스는 보통 Docker daemon입니다. Linux에서는 dockerd가 별도의 백그라운드 서비스로 실행되고, Windows와 macOS의 Docker Desktop도 내부 VM 또는 가상 네트워크 계층을 거쳐 레지스트리에 접속합니다. 따라서 브라우저와 터미널에서 일반 웹사이트가 잘 열려도 daemon의 연결은 Clash 프록시를 전혀 거치지 않는 DIRECT 경로로 남을 수 있습니다.

Docker Hub 통신도 단일 도메인에만 의존하지 않습니다. 인증 토큰을 얻기 위한 auth.docker.io, 이미지 매니페스트를 조회하는 registry-1.docker.io, 레이어를 내려받는 CDN 호스트가 서로 나뉘어 동작합니다. 어느 한 단계만 다른 규칙으로 처리되면 로그인은 성공하지만 레이어 다운로드에서 멈추거나, 매니페스트는 보이는데 실제 풀 단계에서 타임아웃이 발생합니다. 이 글은 Docker daemon이 Clash Mihomo의 투명 프록시를 통과하도록 만들고, 필요한 Docker Hub 트래픽만 선택적으로 프록시 그룹에 보내는 절차를 설명합니다.

적용 범위: 이 글은 허용된 네트워크에서 Docker Hub 연결 품질을 점검하고 안정화하는 기술 설정을 다룹니다. 회사·학교의 보안 정책, 컨테이너 레지스트리 이용 약관, 거주 지역의 관련 법규를 먼저 확인하세요.

Docker daemon과 Clash 사이의 통신 구조 확인

설정을 시작하기 전에 현재 Docker가 어디에서 실행되는지부터 구분해야 합니다. Linux에서 docker version을 실행하면 Client와 Server 정보가 함께 표시됩니다. Client는 현재 셸에서 실행되는 명령이고, Server가 바로 실제 이미지 요청을 처리하는 daemon입니다. docker context ls에서 원격 컨텍스트가 선택되어 있다면, 로컬 PC의 Clash를 아무리 조정해도 원격 Docker 호스트의 연결에는 영향을 주지 않습니다.

Windows와 macOS의 Docker Desktop에서는 상황이 더 복잡합니다. Docker CLI는 호스트에서 실행되지만 daemon은 내부 Linux VM 안에서 실행될 수 있습니다. 호스트의 시스템 프록시를 켜는 것과 daemon의 이미지 프록시를 설정하는 것은 별개의 작업입니다. Docker Desktop 설정에 있는 프록시 항목이 있다면 그 값을 확인하고, TUN 모드를 사용하는 경우에는 Docker Desktop의 가상 어댑터와 DNS 요청이 Clash에 의해 관찰되는지도 함께 확인해야 합니다.

또 하나의 핵심은 DNS입니다. registry-1.docker.io가 호스트에서 해석되는지, daemon이 사용하는 resolver에서 해석되는지, 그리고 Clash의 fake-ip 또는 redir-host 응답이 컨테이너 네트워크와 충돌하지 않는지를 나눠 봐야 합니다. DNS가 실패하면 연결 로그에 도메인이 제대로 표시되지 않고 IP 주소만 남을 수 있습니다. 반대로 DNS는 성공하지만 TLS 연결이 DIRECT로 빠지면 레지스트리 응답을 기다리다가 풀 타임아웃이 발생합니다.

  • Client 문제: 잘못된 Docker context, 인증 정보 손상, CLI 자체의 환경 변수 오류가 원인일 수 있습니다.
  • Daemon 문제: dockerd가 프록시 변수를 읽지 못하거나 서비스 재시작 전의 오래된 환경을 사용하고 있을 수 있습니다.
  • DNS 문제: Docker Hub 호스트가 해석되지 않거나 내부 DNS가 잘못된 주소를 반환할 수 있습니다.
  • 라우팅 문제: 인증·레지스트리·CDN 요청이 서로 다른 정책 또는 서로 다른 노드로 분리될 수 있습니다.
  • 노드 문제: TCP 연결은 성립하지만 큰 레이어 전송 중 속도가 급격히 떨어지거나 연결이 리셋될 수 있습니다.

Clash Mihomo 투명 프록시를 켜기 전 준비할 항목

Clash Verge Rev, Mihomo Party, Clash Nyanpasu 등 Mihomo 기반 클라이언트에서는 보통 TUN 모드를 통해 애플리케이션이 시스템 프록시를 무시하더라도 트래픽을 가상 인터페이스로 끌어올 수 있습니다. 단순히 시스템 프록시의 HTTP 포트만 지정하면 브라우저와 일부 CLI는 작동해도 Docker daemon, 백그라운드 서비스, 컨테이너 내부 프로세스에는 적용되지 않을 수 있습니다. Docker 전체를 프록시화하려면 TUN의 자동 라우팅과 DNS 설정을 먼저 점검하는 편이 안전합니다.

설정 전에 현재 상태를 기록해 두면 문제가 생겼을 때 되돌리기 쉽습니다. 사용 중인 Clash의 혼합 포트, TUN 스택, DNS 모드, 모드 설정이 무엇인지 메모하고, Docker가 이미 실행 중인 상태에서 어떤 호스트가 연결되는지 로그를 확인하세요. 아무 로그도 나타나지 않는다면 규칙보다 먼저 트래픽이 Clash에 도달하지 않는 구조를 의심해야 합니다.

  1. Docker context 확인: docker context showdocker context ls로 현재 daemon이 로컬인지 원격인지 확인합니다.
  2. Clash 프로필 백업: 구독 프로필을 직접 덮어쓰기보다 별도의 오버라이드 또는 로컬 설정 파일을 사용해 규칙을 추가합니다.
  3. TUN 활성화: 클라이언트 설정에서 TUN을 켜고 자동 라우팅과 시스템 권한 요청을 승인합니다. 이미 다른 VPN이나 가상 어댑터가 있다면 충돌 여부를 확인합니다.
  4. DNS 방식 선택: fake-ip를 사용한다면 Docker Desktop과 로컬 resolver가 해당 응답을 올바르게 처리하는지 확인하고, 문제가 반복되면 redir-host 또는 호환성이 높은 DNS 모드로 비교 테스트합니다.
  5. 프록시 그룹 준비: Docker 전용 그룹을 만들거나 기존의 안정적인 프록시 그룹을 선택해 인증·레지스트리·CDN이 같은 정책을 공유하도록 구성합니다.

주의: TUN 모드를 켜면 Docker Hub뿐 아니라 다른 시스템 트래픽도 Clash 규칙의 영향을 받을 수 있습니다. 모든 연결을 무조건 프록시로 보내기보다 Docker 관련 도메인만 별도 그룹으로 분기하고, 나머지는 기존 정책을 유지하는 방식이 장애 범위를 줄입니다.

Docker Hub 도메인과 선택적 분기 규칙 작성

Docker Hub 규칙은 지나치게 넓게 작성하면 관리가 어려워집니다. DOMAIN-SUFFIX,docker.io만 추가하면 관련 호스트를 상당 부분 잡을 수 있지만, 인증과 레이어 CDN이 다른 도메인을 사용할 수 있으므로 실제 Clash 로그를 기준으로 보강해야 합니다. 먼저 기본적으로 다음 호스트를 관찰합니다.

  • auth.docker.io: Docker Hub 인증 토큰 발급에 사용됩니다.
  • registry-1.docker.io: 이미지 매니페스트와 레지스트리 API 요청의 중심입니다.
  • index.docker.io: 일부 레거시 클라이언트나 메타데이터 요청에서 보일 수 있습니다.
  • docker.io, hub.docker.com: 웹 페이지와 보조 API 요청에 사용될 수 있습니다.
  • 레이어 다운로드 CDN: 이미지와 저장소, 제공업체 정책에 따라 별도의 CDN 도메인이 로그에 나타날 수 있습니다.

사용 중인 설정 문법에 맞춰 전용 그룹과 규칙을 구성할 수 있습니다. 아래는 개념을 보여 주는 예시이며, 실제 그룹 이름과 프록시 이름은 자신의 프로필에 맞게 바꿔야 합니다.

proxy-groups:
  - name: DOCKER-HUB
    type: select
    proxies:
      - Proxy
      - DIRECT

rules:
  - DOMAIN,auth.docker.io,DOCKER-HUB
  - DOMAIN,registry-1.docker.io,DOCKER-HUB
  - DOMAIN-SUFFIX,docker.io,DOCKER-HUB
  - DOMAIN-SUFFIX,hub.docker.com,DOCKER-HUB
  - MATCH,DIRECT

이 규칙을 무조건 복사하기보다 두 단계로 적용하는 것이 좋습니다. 처음에는 auth.docker.ioregistry-1.docker.io만 전용 그룹에 넣고, 이미지 레이어 다운로드 시 실제로 나타나는 CDN 호스트를 로그에서 확인한 뒤 한 줄씩 추가합니다. 이렇게 하면 DOMAIN-KEYWORD,docker처럼 범위가 넓은 규칙 때문에 무관한 개발 도구나 사설 레지스트리까지 같은 노드로 끌려가는 일을 피할 수 있습니다.

규칙 순서도 중요합니다. 구독 YAML에 앞쪽에서 GEOIP,LAN,DIRECT, 특정 국가의 GEOIP,DIRECT, 또는 광범위한 FINAL,DIRECT와 유사한 정책이 먼저 평가되면 Docker 전용 규칙에 도달하지 않을 수 있습니다. Clash는 일반적으로 위에서 아래로 규칙을 평가하므로, Docker Hub의 명시적 도메인 규칙을 적절한 위치에 배치하고 연결 로그에서 실제 매칭 규칙을 확인해야 합니다.

Docker daemon에 프록시를 적용하는 방법

TUN이 Docker daemon의 연결을 확실히 잡아 주는 환경도 있지만, 운영체제와 Docker 설치 방식에 따라 daemon에 명시적인 프록시를 지정하는 편이 더 예측 가능합니다. 특히 Linux의 systemd 서비스로 실행되는 dockerd는 현재 셸의 HTTP_PROXYHTTPS_PROXY를 자동으로 상속하지 않습니다. 서비스 환경에 프록시를 지정한 뒤 daemon을 다시 시작해야 합니다.

Clash의 혼합 포트가 로컬에서 127.0.0.1:7890으로 열려 있다고 가정하면, Docker daemon이 해당 주소에 접근할 수 있는지부터 확인합니다. Linux 호스트의 daemon이라면 loopback 주소를 사용할 수 있지만, Docker Desktop 내부 VM이나 별도 원격 daemon에서는 호스트의 127.0.0.1이 다른 공간을 가리킵니다. 이 경우 Docker Desktop이 제공하는 호스트 게이트웨이 주소나 Clash가 바인딩한 LAN 주소를 사용해야 하며, 외부에 프록시 포트를 노출할 때는 방화벽으로 접근 범위를 제한해야 합니다.

# systemd drop-in 예시
sudo systemctl edit docker

[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,.local"

# 저장 후 적용
sudo systemctl daemon-reload
sudo systemctl restart docker

환경 변수 방식의 장점은 daemon이 생성하는 이미지 레이어 요청에 일관되게 적용된다는 점입니다. 그러나 사설 레지스트리, 사내 도메인, 로컬 미러를 프록시로 보내면 안 되는 환경이라면 NO_PROXY를 반드시 세밀하게 작성해야 합니다. NO_PROXY는 Docker Hub 규칙을 대체하는 것이 아니라, 프록시를 사용하지 않을 내부 주소를 정의하는 예외 목록입니다. 설정 후에는 daemon이 새 환경을 읽었는지 서비스 상태와 로그로 확인하세요.

Docker Desktop 사용자는 Linux의 systemd 파일을 그대로 수정하면 안 됩니다. Docker Desktop의 Settings에서 Resources 또는 Proxies 관련 항목을 확인하고, 조직 정책으로 관리되는 PC라면 관리자에게 허용된 프록시 주소와 인증 방식을 문의해야 합니다. 호스트 Clash의 혼합 포트를 Docker Desktop이 접근하지 못하는 경우에는 TUN 자동 라우팅, Docker Desktop 전용 프록시 입력란, 호스트 게이트웨이 주소를 각각 분리해 테스트하는 것이 좋습니다.

docker pull 재현과 로그 기반 문제 해결

설정이 끝났다면 큰 이미지를 바로 받기보다 작은 공개 이미지를 사용해 단계별로 검증합니다. 먼저 DNS 해석, HTTPS 응답, Docker 인증, 실제 레이어 다운로드를 순서대로 나누면 어느 구간에서 실패하는지 빠르게 확인할 수 있습니다. 브라우저의 성공 여부가 아니라 Docker daemon이 남기는 결과와 Clash 연결 로그를 기준으로 판단해야 합니다.

  1. DNS 확인: 호스트에서 nslookup registry-1.docker.io 또는 dig registry-1.docker.io를 실행하고, Clash 로그에 같은 도메인의 DNS 처리 흔적이 있는지 확인합니다.
  2. 인증 경로 확인: docker login을 실행한 뒤 auth.docker.io가 DOCKER-HUB 그룹으로 매칭되는지 봅니다. 로그인만 성공해도 레이어 경로가 별도로 실패할 수 있으므로 다음 단계까지 진행합니다.
  3. 작은 이미지로 풀 테스트: docker pull hello-world처럼 용량이 작은 이미지로 기본 연결을 확인합니다.
  4. 큰 레이어 재현: 문제가 발생했던 이미지에서 다시 풀을 실행하고, 타임아웃 직전 Clash 로그에 표시되는 FQDN과 매칭 규칙을 기록합니다.
  5. 캐시를 구분: 이미 내려받은 이미지 때문에 성공한 것처럼 보이지 않도록 다른 태그나 테스트용 이미지로 새 레이어 다운로드를 확인합니다.

auth.docker.io만 프록시로 가고 registry-1.docker.io가 DIRECT라면 인증 성공 후 매니페스트 조회에서 멈출 수 있습니다. 반대로 매니페스트까지 성공하지만 레이어에서만 멈춘다면 CDN 호스트가 누락되었거나, 선택한 노드가 큰 파일 전송에 약할 가능성이 있습니다. 이때는 규칙을 무작정 넓히기보다 해당 시간대의 Clash 연결 목록에서 실제 목적지와 연결 시간을 확인하고, 다른 프록시 그룹으로 한 번만 비교하는 방식이 효율적입니다.

DNS 오류와 TLS 오류도 구분해야 합니다. 이름 해석 자체가 실패하면 lookup ... no such host나 resolver 관련 메시지가 나타나고, TLS 단계의 문제라면 인증서, 연결 리셋, 핸드셰이크 지연이 로그에 남습니다. fake-ip 환경에서 특정 Docker 구성 요소가 IP를 직접 사용하거나 인증서 검증 대상과 실제 연결 주소가 어긋나는 경우에는 DNS 모드를 바꾸어 비교하세요. 단, 문제를 확인한다고 해서 인증서 검증을 끄거나 insecure 옵션을 상시 활성화하는 것은 권장하지 않습니다.

기록할 항목: 재현 시각, 이미지 이름과 태그, Docker context, Clash 모드, DNS 모드, 매칭된 FQDN, 사용된 프록시 노드, 오류가 발생한 단계와 소요 시간을 함께 남기면 노드 문제와 규칙 문제를 쉽게 분리할 수 있습니다.

안정적인 Docker Hub 프록시 운영 체크리스트

한 번 풀에 성공했다고 설정이 끝난 것은 아닙니다. 구독 규칙이 갱신되면서 사용자 규칙의 위치가 바뀌거나, Docker Hub가 레이어 CDN을 변경하거나, 노드가 교체되면 같은 오류가 다시 나타날 수 있습니다. 따라서 전용 규칙은 별도의 오버라이드 파일에 보관하고, 구독을 갱신한 뒤에도 Docker 관련 규칙이 최종 설정에 남아 있는지 확인하세요.

노드 선택에서는 단순한 핑 수치보다 안정적인 장시간 전송을 우선해야 합니다. Docker 이미지는 작은 인증 요청과 수백 MB에 달하는 레이어 다운로드가 연속으로 발생하므로, 짧은 지연 시간이 낮더라도 지속적인 TCP 처리량이 낮으면 풀 시간이 길어집니다. 여러 노드를 번갈아 사용하면서 인증 단계, 매니페스트 단계, 레이어 단계의 결과를 분리해 비교하면 실제로 Docker에 적합한 출구를 찾을 수 있습니다.

사내 레지스트리나 Docker Hub 미러를 함께 사용하는 경우에는 모든 컨테이너 트래픽을 하나의 그룹으로 묶지 않는 편이 좋습니다. 예를 들어 내부 레지스트리는 DIRECT 또는 사내 네트워크 전용 그룹으로 보내고, 공개 Docker Hub만 DOCKER-HUB 그룹에 연결하면 불필요한 우회와 인증 지연을 줄일 수 있습니다. 개발 서버가 여러 대라면 각 서버의 daemon 설정과 DNS 구성이 동일한지도 확인해야 합니다.

시중의 일부 GUI 도구는 브라우저용 시스템 프록시만 쉽게 켤 수 있고, Docker Desktop VM이나 Linux daemon의 실제 경로를 설명하지 않아 사용자가 같은 설정을 여러 번 반복하게 만듭니다. 반대로 단순 환경 변수만 안내하는 방식은 TUN·DNS·CDN 규칙을 놓치기 쉽습니다. Clash 공식 사이트는 Docker daemon과 Mihomo TUN의 차이, Docker Hub 인증·레지스트리·레이어 경로의 분리, 로그를 이용한 선택적 규칙 검증을 한 흐름으로 정리해 시행착오를 줄이는 데 초점을 둡니다. Docker pull 타임아웃을 체계적으로 점검할 수 있는 Clash 환경을 직접 구성해 보고 싶다면, 필요한 클라이언트와 설정 자료를 확인할 수 있는 Clash 공식 사이트 다운로드 페이지에서 시작해 보세요.