웹 브라우저는 되는데 Docker 이미지만 멈추는 이유
브라우저에서 웹사이트가 정상적으로 열리는데도 docker pull이 멈추거나 context deadline exceeded, i/o timeout, TLS handshake timeout 오류를 내는 경우가 있습니다. 이 현상은 Clash 노드가 반드시 고장 났다는 뜻이 아닙니다. 브라우저와 Docker 데몬이 서로 다른 네트워크 경로와 프록시 설정을 사용하기 때문에, 한쪽은 정상이고 다른 쪽만 실패할 수 있습니다.
가장 중요한 차이는 Docker 명령이 실제 다운로드를 직접 수행하지 않는다는 점입니다. 터미널에서 실행한 docker pull은 Docker 데몬에 요청을 전달하고, 백그라운드에서 실행 중인 데몬이 Docker Hub의 레지스트리와 인증 서버에 연결해 이미지 매니페스트와 레이어를 내려받습니다. 따라서 셸에 HTTP_PROXY와 HTTPS_PROXY를 설정했거나 Clash의 시스템 프록시를 켰다고 해서 Docker 데몬까지 자동으로 프록시를 사용하는 것은 아닙니다.
Docker Hub 접근도 하나의 주소만 사용하는 단순한 구조가 아닙니다. 보통 registry-1.docker.io에서 매니페스트를 요청하고, auth.docker.io에서 인증 토큰을 받은 뒤, 실제 레이어는 CDN 또는 저장소 도메인에서 다운로드합니다. 이 중 한 도메인만 규칙에서 잘못 처리되거나 DNS 응답이 비정상적이면 로그인은 성공하지만 이미지 다운로드가 멈추거나, 첫 레이어 이후 다음 레이어에서 진행률이 더 이상 올라가지 않을 수 있습니다.
브라우저의 프록시 정상 작동 여부만으로 Docker 네트워크가 정상이라고 판단하지 마세요. 먼저 Docker 데몬이 실행 중인 위치와 Clash가 트래픽을 가로채는 계층을 분리해서 확인해야 합니다.
Docker 데몬의 실행 위치와 네트워크 경로 확인
첫 단계는 Docker가 어디에서 실행되고 있는지 확인하는 것입니다. Linux에서 Docker Engine을 직접 설치했다면 dockerd가 호스트 운영체제의 systemd 서비스로 실행됩니다. 이 경우 Docker 데몬은 일반 사용자 셸의 환경 변수나 데스크톱 클라이언트의 시스템 프록시 설정을 그대로 상속하지 않을 수 있습니다. 반면 Windows와 macOS의 Docker Desktop은 별도의 Linux 가상 머신 안에서 엔진을 실행하므로, 호스트에서 켠 Clash 설정과 가상 머신 내부의 연결 경로가 분리될 수 있습니다.
- Linux에서는
docker info로 서버 정보가 반환되는지 확인하고,systemctl status docker로 데몬의 실행 상태를 확인합니다. - Docker Desktop에서는 컨테이너와 이미지가 호스트의 일반 프로세스가 아니라 Docker Desktop 내부 엔진에서 처리된다는 점을 기억해야 합니다.
- 원격 Docker 호스트를 사용한다면
dockerCLI가 설치된 컴퓨터가 아니라 원격 데몬이 인터넷에 연결하는 주체입니다. - Docker 데몬이 다른 가상 머신, WSL2 배포판 또는 원격 서버에 있다면 해당 환경의 라우팅과 DNS도 별도로 점검해야 합니다.
간단한 분리 테스트로 Docker Hub의 레지스트리 주소를 직접 확인할 수 있습니다. 응답 코드가 401 Unauthorized로 나오는 것은 오히려 서버에 도달했다는 의미이므로 네트워크 경로가 완전히 막힌 것은 아닙니다. 반대로 이름 해석 실패나 연결 시간 초과가 발생하면 인증 이전 단계에서 문제가 생긴 것입니다.
getent hosts registry-1.docker.io
curl -I https://registry-1.docker.io/v2/
docker version
단, 위의 curl은 현재 셸의 네트워크 경로를 확인할 뿐 Docker 데몬의 경로를 완전히 대변하지는 않습니다. Linux에서는 데몬 로그와 systemd 환경을 함께 확인하고, Docker Desktop에서는 앱의 진단 로그와 내장 네트워크 설정을 확인해야 합니다.
Clash TUN 모드로 데몬 트래픽을 가로채는 구성
Docker 데몬에 별도의 프록시를 지정하기 어려운 환경에서는 Clash 또는 mihomo의 TUN 모드를 사용할 수 있습니다. TUN은 애플리케이션이 프록시를 인식하는지에 의존하지 않고, 호스트의 IP 트래픽을 가상 네트워크 인터페이스로 받아 규칙에 따라 처리합니다. 이 방식은 브라우저뿐 아니라 프록시 환경 변수를 읽지 않는 서비스 프로세스에도 적용할 수 있다는 장점이 있습니다.
다만 TUN을 켰다고 모든 트래픽이 자동으로 외부 프록시를 통과하는 것은 아닙니다. TUN 설정의 자동 라우팅, DNS 처리, 기본 정책, Clash 프로세스의 우회 경로가 함께 맞아야 합니다. 사용하는 클라이언트의 화면에서 TUN 모드를 활성화한 뒤, 설정 파일에 해당 기능이 실제로 적용되었는지 확인하세요. mihomo 계열 설정의 기본 형태는 다음과 같습니다.
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns:
enable: true
enhanced-mode: fake-ip
nameserver:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
클라이언트와 코어 버전에 따라 지원 필드와 기본값이 다를 수 있으므로, 위 내용을 무조건 그대로 붙여넣기보다 현재 코어가 허용하는 설정인지 먼저 확인해야 합니다. Windows에서는 Wintun 드라이버와 관리자 권한이 필요할 수 있고, Linux에서는 라우팅 테이블과 권한 정책이 영향을 줍니다. TUN을 켠 뒤 일반 웹사이트는 되는데 Docker만 계속 멈춘다면 Docker가 별도 가상 네트워크 안에서 실행되는지, 해당 인터페이스의 트래픽이 호스트 TUN 경로를 통과하는지 확인해야 합니다.
Docker Desktop의 Linux 가상 머신 내부 트래픽은 호스트 애플리케이션과 동일하게 처리되지 않을 수 있습니다. 호스트의 TUN만 켜고 해결되지 않는다면 Docker Desktop의 자체 프록시 설정이나 데몬 프록시 설정을 함께 검토하세요.
Linux Docker 데몬에 HTTP 프록시를 직접 지정하기
TUN 전체 가로채기보다 Docker 데몬에 명시적으로 프록시를 지정하는 방법이 더 예측 가능한 경우가 많습니다. Clash의 혼합 포트 또는 HTTP 프록시 포트가 로컬에서 열려 있다면, Docker 데몬이 해당 주소를 사용하도록 systemd 드롭인 설정을 만들 수 있습니다. 여기서 중요한 점은 127.0.0.1의 의미입니다. Docker 데몬이 호스트에서 실행될 때는 호스트 자신을 가리키지만, Docker Desktop 내부나 별도 컨테이너에서 데몬이 실행될 때는 다른 네트워크 공간을 가리킬 수 있습니다.
- Clash에서 HTTP 또는 mixed 포트가 실제로 리스닝 중인지 확인합니다. 일반적으로 로컬 주소와 포트는 클라이언트 화면의 포트 설정에서 확인할 수 있습니다.
- Docker 서비스용 systemd 디렉터리를 만들고 프록시 환경 변수를 지정합니다.
- systemd 설정을 다시 읽은 뒤 Docker 데몬을 재시작합니다.
docker info의 서버 정보와 데몬 로그를 확인한 다음 이미지를 다시 요청합니다.
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf <<'EOF'
[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"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info
Docker 데몬이 프록시에 연결할 때는 일반적으로 HTTPS 목적지로 향하는 HTTP CONNECT 프록시 주소를 사용합니다. Clash에서 SOCKS 포트만 열려 있다면 Docker 설정에 SOCKS 주소를 바로 넣기보다 HTTP 또는 mixed 포트를 사용하는 편이 호환성이 좋습니다. 사설 레지스트리, 사내 네트워크, Docker 소켓 주소를 프록시에서 제외해야 한다면 NO_PROXY에 해당 도메인과 IP 대역을 추가하세요.
프록시 설정을 바꾼 뒤에도 기존에 실패한 연결이 남아 있을 수 있으므로, 테스트 과정에서는 데몬 재시작 후 새로 docker pull을 실행하는 것이 좋습니다. 설정 파일이 적용되지 않는다면 systemctl show docker --property=Environment로 실제 서비스 환경에 변수가 들어갔는지 확인하세요.
Docker Desktop과 WSL2에서 따로 확인할 항목
Windows 또는 macOS의 Docker Desktop을 사용하는 경우에는 Linux systemd 예시를 그대로 적용할 수 없습니다. Docker Desktop은 내부 엔진을 관리하므로 호스트의 Docker 서비스 파일을 수정해도 실제 이미지 다운로드 경로에는 아무 영향이 없을 수 있습니다. 먼저 Docker Desktop 설정에서 프록시 관련 항목을 찾고, 수동 프록시 주소가 필요한지 자동 프록시 감지가 제대로 동작하는지 확인하세요.
Docker Desktop과 Clash를 함께 사용할 때는 다음 세 가지 주소를 혼동하지 않아야 합니다. 첫째는 호스트에서 Clash가 리스닝하는 주소, 둘째는 Docker Desktop 내부 엔진이 호스트에 접근할 때 사용하는 게이트웨이 주소, 셋째는 컨테이너 안에서 바라보는 호스트 주소입니다. 컨테이너의 127.0.0.1은 호스트의 localhost가 아니므로, 컨테이너 실행 옵션이나 Docker Desktop이 제공하는 호스트 별칭을 별도로 사용해야 합니다.
- Docker Desktop의 프록시 설정을 바꾼 뒤에는 엔진 재시작이 필요한지 확인합니다.
- WSL2에서 직접 Docker Engine을 실행한다면 WSL2 내부의 Clash 접근 주소와 라우팅 테이블을 확인합니다.
- Docker Desktop 엔진과 WSL2 내부 엔진을 동시에 실행하지 않습니다. 어느 데몬에 CLI가 연결되어 있는지
docker context ls로 확인합니다. - 기업 네트워크에서는 Docker Hub 인증 서버와 CDN 도메인에 대한 TLS 검사 또는 방화벽 정책이 별도로 적용될 수 있습니다.
특정 컨테이너의 외부 통신과 Docker 이미지 풀은 서로 다른 문제입니다. 이미지가 내려받아진 뒤 컨테이너가 인터넷에 접속하지 못한다면 컨테이너의 DNS, 브리지 네트워크, 실행 시 프록시 환경 변수까지 추가로 확인해야 합니다. 반대로 이미지 다운로드 자체가 실패한다면 우선 Docker 데몬과 레지스트리 사이의 경로에 집중하세요.
Docker Hub 규칙 우선순위와 DNS 점검
Clash의 규칙은 위에서 아래로 평가되고, 처음 일치한 규칙에서 처리가 종료됩니다. Docker Hub를 프록시로 보내고 싶다면 뒤쪽의 포괄적인 GEOIP, IP-CIDR, MATCH 규칙보다 앞에 도메인 규칙을 배치해야 합니다. Docker 관련 도메인을 먼저 DOMAIN-SUFFIX로 지정하고, 마지막에 기본 정책을 두는 구조가 조사하기 쉽습니다.
rules:
- DOMAIN-SUFFIX,docker.io,PROXY
- DOMAIN-SUFFIX,docker.com,PROXY
- DOMAIN,auth.docker.io,PROXY
- DOMAIN,registry-1.docker.io,PROXY
- MATCH,DIRECT
여기서 PROXY는 실제 설정에 선언된 프록시 그룹 이름으로 바꿔야 합니다. 그룹명이 Proxy나 자동 선택으로 되어 있는데 규칙에는 PROXY라고 쓰면 원하는 그룹으로 연결되지 않습니다. 또한 실제 레이어를 제공하는 CDN 도메인이 별도 규칙에 의해 직결로 처리되면 로그인과 매니페스트 요청은 성공해도 레이어 다운로드만 멈출 수 있습니다. 로그에서 실패한 호스트명을 확인한 뒤 필요한 도메인을 좁혀 추가하세요.
DNS 모드도 함께 확인해야 합니다. fake-IP 모드에서는 도메인이 가상 주소로 응답되고 Clash가 연결 시 원래 도메인을 복원합니다. 일부 Docker 구성이나 특수한 네트워크 라이브러리가 이 동작을 제대로 처리하지 못하면, Docker 관련 도메인을 fake-ip-filter에 넣거나 redir-host 모드로 비교 테스트할 수 있습니다. DNS 설정을 바꿀 때는 캐시가 남아 결과가 즉시 바뀌지 않을 수 있으므로 Clash DNS 캐시와 Docker 데몬을 함께 재시작한 뒤 다시 확인하세요.
로그로 실패 지점을 좁히는 실전 순서
무작정 노드를 바꾸기보다 요청이 어느 단계에서 멈추는지 기록하는 것이 빠릅니다. 먼저 Clash 로그 수준을 임시로 info 또는 필요한 경우 debug로 올리고, 동시에 Docker 데몬 로그를 관찰합니다. 정상적인 흐름에서는 인증 도메인, 레지스트리 도메인, 레이어 CDN 도메인에 대한 연결 기록이 순서대로 나타납니다.
- DNS 오류: 호스트명을 해석하지 못하거나 잘못된 IP가 반환됩니다. Clash DNS 설정과
fake-ip예외 목록을 확인합니다. - 연결 시간 초과: 해당 도메인이 직결로 빠졌거나, TUN 경로와 Docker 가상 인터페이스가 연결되지 않았을 가능성이 있습니다.
- TLS handshake timeout: 프록시 포트 유형이 맞지 않거나 중간 장비의 TLS 검사, 노드 품질 문제가 원인일 수 있습니다.
- 401 응답: 레지스트리에 도달했지만 인증 토큰이 필요하다는 뜻입니다. 네트워크 차단으로 단정하지 말고 다음 인증 요청을 확인합니다.
- 레이어 다운로드만 정지: CDN 도메인이 다른 규칙을 타거나, Docker 데몬의 프록시가 대용량 연결을 안정적으로 유지하지 못하는지 확인합니다.
sudo journalctl -u docker -f
docker pull alpine:latest
docker system info
테스트 이미지는 작은 alpine이나 busybox처럼 레이어 수와 용량이 작은 것을 선택하는 편이 좋습니다. 작은 이미지도 실패하면 경로와 DNS 문제일 가능성이 높고, 작은 이미지는 성공하지만 큰 이미지에서만 끊긴다면 노드 안정성, 프록시의 연결 유지 시간, MTU 또는 중간 방화벽의 대용량 전송 정책을 추가로 의심할 수 있습니다. 점검이 끝나면 로그 수준을 원래 값으로 되돌리고, 글로벌 모드로 임시 변경했다면 규칙 모드로 복구하세요.