Docker 컨테이너에서만 연결이 실패하는 이유
호스트 운영체제에서는 npm 설치와 GitHub 접속이 정상인데, 같은 명령을 Docker 컨테이너에서 실행하면 ETIMEDOUT, ECONNRESET, Could not resolve host 같은 오류가 발생하는 경우가 많습니다. 이는 단순히 Clash 노드의 품질이 나빠서가 아니라, 호스트와 컨테이너가 서로 다른 네트워크 경로와 DNS 환경을 사용하기 때문입니다.
Clash의 일반적인 시스템 프록시 설정은 브라우저나 운영체제 프록시를 인식하는 애플리케이션에는 잘 적용됩니다. 그러나 Docker 컨테이너는 별도의 네트워크 네임스페이스 안에서 실행되며, 기본적으로 Docker 브리지와 호스트의 DNS 전달자를 사용합니다. 따라서 컨테이너 내부의 Node.js, Git, curl, 패키지 매니저가 생성하는 연결이 Clash의 HTTP 또는 SOCKS 포트를 자동으로 통과한다고 보장할 수 없습니다.
특히 npm은 레지스트리에서 여러 파일을 내려받고, GitHub는 HTTPS와 Git 프로토콜을 함께 사용하며, Docker Hub는 인증·이미지 매니페스트·레이어 다운로드를 여러 도메인으로 나누어 처리합니다. 어느 한 단계만 직접 연결되거나 DNS가 다른 경로로 나가면 로그인은 성공했지만 이미지 다운로드가 실패하는 식의 복합적인 문제가 나타납니다.
먼저 확인할 핵심
컨테이너에 HTTP_PROXY 환경 변수만 넣는 방식은 모든 트래픽을 가로채지 못합니다. 일반 프록시 설정과 TUN·iptables 기반 투명 프록시는 적용 범위가 서로 다르므로 목적에 맞는 방식을 선택해야 합니다.
- 호스트는 연결되지만 컨테이너만 실패: Docker 네트워크 또는 DNS 경로를 우선 점검합니다.
- 도메인 조회부터 실패: 프록시보다 먼저 컨테이너의 DNS 설정과 fake-ip 예외를 확인합니다.
- npm은 되지만 Git clone만 실패: HTTPS와 SSH의 포트·프로토콜 차이를 확인합니다.
- Docker pull만 실패: Docker 데몬 자체가 컨테이너와 다른 프록시 설정을 사용하고 있을 수 있습니다.
1Clash와 Docker의 패킷 흐름 이해
구성을 시작하기 전에 패킷이 실제로 어디를 통과하는지 이해해야 합니다. 데스크톱에서 Clash Verge Rev 또는 Mihomo의 시스템 프록시를 켜면 브라우저가 HTTP proxy 또는 SOCKS5 주소를 읽어 요청을 전달합니다. 반면 컨테이너는 보통 172.17.0.0/16과 같은 Docker 브리지 대역에서 출발해 호스트의 NAT를 거쳐 외부로 나갑니다. 이 경로에 Clash가 명시적으로 개입하지 않으면 컨테이너 패킷은 Clash의 규칙을 거치지 않습니다.
구성 방식은 크게 세 가지로 나눌 수 있습니다. 첫째, 가장 간단한 애플리케이션 프록시 방식은 컨테이너에 HTTP_PROXY와 HTTPS_PROXY를 전달합니다. npm, curl처럼 프록시 환경 변수를 이해하는 프로그램에는 효과적이지만, 모든 바이너리와 UDP 연결을 처리하지는 못합니다. 둘째, 호스트의 Clash가 제공하는 TUN 모드를 사용하면 운영체제 수준에서 라우팅되는 트래픽을 가상 인터페이스로 받아 규칙에 따라 분기할 수 있습니다. 셋째, Linux 서버에서는 Docker 브리지 패킷을 Clash의 투명 포트로 보내도록 iptables 또는 nftables 규칙을 구성할 수 있습니다.
환경에 맞는 방식 선택
- 개인 개발 PC: 먼저 컨테이너별 환경 변수 프록시를 시도하고, 해결되지 않는 프로세스가 있으면 TUN 모드를 선택합니다.
- Linux 개발 서버: Mihomo의 redir 또는 tproxy 포트와 iptables를 조합하면 여러 컨테이너를 한 번에 처리할 수 있습니다.
- Docker Desktop: Linux VM을 거치므로 호스트의
127.0.0.1이 컨테이너에서 호스트를 의미하지 않을 수 있습니다.host.docker.internal사용 가능 여부를 확인해야 합니다. - SSH 기반 Git 사용: 투명 프록시가 TCP 22번을 지원하지 않으면 GitHub의 SSH 접속 대신 HTTPS 또는
ssh.github.com:443경로를 고려합니다.
루프백 주소 주의
컨테이너 내부의 127.0.0.1은 호스트가 아니라 해당 컨테이너 자신입니다. 호스트에서 Clash가 7890 포트를 열고 있어도 컨테이너가 같은 주소로 접근할 수 있는 것은 아닙니다.
2컨테이너 프록시 환경 변수 구성
가장 빠르게 검증할 수 있는 방법은 개발 컨테이너에 프록시 환경 변수를 주입하는 것입니다. Clash의 HTTP 포트가 7890, SOCKS 포트가 7891이라고 가정하겠습니다. 실제 포트는 Clash Verge Rev 또는 Mihomo의 설정 화면에서 확인한 뒤 바꾸어야 합니다. 컨테이너에서 접근할 호스트 주소도 운영체제에 따라 달라질 수 있습니다.
compose.yaml의 서비스에 다음과 같이 환경 변수를 추가합니다.
extra_hosts는 Linux Docker Engine에서 호스트 게이트웨이 이름을 해석하는 데 도움이 됩니다. Docker Desktop에서는 이미 제공되는 경우 중복 여부를 확인하세요.
프록시를 무조건 모든 내부 주소에 적용하면 사설 레지스트리, 데이터베이스, 사내 Git 서버가 외부 노드로 전달될 수 있습니다. 따라서 NO_PROXY에 localhost, 서비스명, 사설 도메인, 사내 IP 대역에 해당하는 호스트를 추가하는 것이 안전합니다. 예를 들어 내부 Git 서버가 git.corp.example이라면 NO_PROXY=git.corp.example,.corp.example처럼 작성합니다.
npm·Git·curl 연결 테스트
컨테이너를 시작한 뒤 먼저 DNS, HTTPS, npm, Git 순서로 테스트하면 원인을 빠르게 좁힐 수 있습니다.
npm 설정이 셸 환경 변수와 다르게 저장되어 있다면 다음 명령으로 명시할 수 있습니다. 단, 이미지 레이어에 프록시 인증 정보가 남지 않도록 비밀번호를 Dockerfile에 직접 기록하지 마세요.
중간 점검 결과
이 단계에서 npm과 HTTPS 요청이 성공한다면 애플리케이션 프록시 구성은 완료된 것입니다. 여전히 특정 바이너리만 실패하거나 UDP·비표준 포트가 필요하다면 TUN 또는 투명 리다이렉트 방식으로 확장하세요.
3TUN 또는 iptables로 투명 프록시 적용
환경 변수를 지원하지 않는 도구까지 처리하려면 Clash가 패킷을 직접 받아야 합니다. Clash Verge Rev와 Mihomo에서는 TUN 모드를 활성화하고 자동 라우팅을 켜는 방식이 가장 간단합니다. 설정에서 TUN Mode를 켠 뒤 스택은 system 또는 코어가 권장하는 모드로 선택하고, DNS hijack 옵션을 함께 확인합니다. 관리자 권한 또는 시스템 확장 권한을 요구할 수 있으므로 운영체제의 네트워크 권한 요청을 승인해야 합니다.
Mihomo 설정에 투명 프록시 포트를 선언하고, 컨테이너 트래픽이 해당 포트로 향하도록 구성합니다.
그룹 이름 Developer는 실제 프록시 그룹 이름으로 교체해야 합니다. 설정 파일의 들여쓰기는 YAML 문법에 직접 영향을 주므로 공백을 유지하세요.
iptables를 직접 구성하는 경우에는 Docker 브리지 대역의 TCP 패킷을 Clash의 redir 포트로 리다이렉트해야 합니다. 시스템마다 인터페이스 이름과 사용자 ID가 다르므로 아래 예시는 구조를 이해하기 위한 출발점으로 사용하고, 운영 서버에 적용하기 전 테스트 환경에서 검증하세요.
투명 프록시는 원래 목적지 정보를 보존해야 하며, Clash 프로세스 자신이 만든 연결을 다시 가로채면 무한 루프가 발생합니다. 따라서 Clash 실행 사용자의 UID를 제외하고, 로컬·Docker 내부 대역과 브로드캐스트 주소를 반환 규칙으로 제외해야 합니다. UDP까지 처리하려면 redir 포트만으로 부족할 수 있으며, Mihomo의 TUN 또는 TProxy 지원과 커널의 관련 모듈을 함께 검토해야 합니다.
운영 서버에 적용할 때
iptables 규칙을 잘못 추가하면 컨테이너가 외부 네트워크뿐 아니라 DNS, 데이터베이스, 모니터링 서비스에도 접근하지 못할 수 있습니다. 변경 전 iptables-save로 백업하고, SSH 세션이 끊겨도 복구할 수 있는 콘솔을 준비하세요.
4DNS 누수와 규칙 우선순위 점검
투명 프록시가 작동해도 DNS가 Docker 기본 resolver를 통해 직접 나가면 접속 결과가 불안정해질 수 있습니다. Docker 컨테이너의 /etc/resolv.conf에는 보통 Docker의 내부 DNS 주소가 표시되며, 이 resolver가 호스트 DNS를 전달합니다. Clash의 TUN DNS hijack이나 Mihomo DNS 포트를 사용하려면 컨테이너가 실제로 어떤 DNS 서버에 질의하는지 먼저 확인해야 합니다.
fake-ip 모드에서는 특정 도메인이 가상 IP로 응답되며 Clash가 원래 도메인을 기억합니다. 그러나 사내 도메인, 클라우드 메타데이터 주소, 로컬 개발 도메인을 fake-ip으로 처리하면 애플리케이션이 예상하지 못한 주소를 받을 수 있습니다. 이런 도메인은 fake-ip-filter에 추가하고, 내부 DNS가 필요한 경우 해당 nameserver 정책을 분리하세요.
규칙은 위에서 아래로 평가됩니다
Clash 규칙은 일반적으로 위에서 아래 순서로 적용되며 먼저 일치한 규칙이 선택됩니다. 아래처럼 넓은 GEOIP나 MATCH 규칙을 개발 도구 규칙보다 앞에 두면 GitHub나 Docker Hub가 의도하지 않은 경로로 전송될 수 있습니다.
GitHub 관련 요청을 모두 같은 노드로 보내야 하는 것은 아닙니다. 코드 저장소는 지연 시간이 낮은 노드, 대용량 릴리스 파일은 다운로드 속도가 높은 노드, 사내 저장소는 DIRECT로 분리할 수 있습니다. 규칙을 세분화할수록 유지 관리가 어려워지므로 실제 로그에서 확인되는 도메인부터 추가하는 것이 좋습니다.
로그로 최종 확인
Clash의 연결 로그에서 컨테이너의 요청이 보이는지 확인하세요. 요청이 로그에 없으면 규칙 문제가 아니라 TUN·iptables·Docker 라우팅 문제일 가능성이 높고, 로그에는 보이지만 실패하면 노드·DNS·TLS 또는 원격 서버 응답을 확인해야 합니다.
5Docker Hub와 CI 환경의 별도 설정
docker pull은 일반 컨테이너 프로세스가 아니라 Docker 데몬이 실행하는 작업입니다. 따라서 애플리케이션 컨테이너에 HTTP_PROXY를 넣어도 Docker Hub 이미지 다운로드에는 영향을 주지 않을 수 있습니다. Linux에서 systemd로 Docker를 실행한다면 데몬용 drop-in 설정에 프록시를 등록한 뒤 데몬을 재시작해야 합니다.
Docker Desktop은 데몬이 별도의 VM 안에서 실행될 수 있으므로 Linux systemd 경로를 그대로 적용하지 않습니다. Desktop의 프록시 설정 화면 또는 엔진 설정을 사용하고, 설정 변경 후에는 새 컨테이너를 생성해 테스트하세요. CI runner에서는 비밀 변수로 프록시 인증 정보를 전달하고 로그에 URL과 토큰이 출력되지 않도록 주의해야 합니다.
프록시를 사용한 뒤에는 캐시도 확인해야 합니다. 이전에 실패한 레이어나 npm 캐시가 남아 있으면 네트워크를 수정했는데도 같은 오류처럼 보일 수 있습니다. 새로운 컨테이너에서 curl, npm ping, git ls-remote, docker pull을 각각 실행해 성공 여부를 분리해서 기록하면 재현 가능한 개발 환경을 만들 수 있습니다.
6문제별 빠른 진단표
- Could not resolve host:
/etc/resolv.conf, Docker DNS, Clash DNS listen 주소를 확인합니다. - npm만 타임아웃:
npm config get proxy와 HTTPS 프록시 값을 확인하고 레지스트리 주소가 올바른지 검사합니다. - GitHub HTTPS는 되지만 SSH 실패: TCP 22번이 프록시에서 지원되는지 확인하고 HTTPS Git 또는 443 포트를 사용합니다.
- docker pull만 실패: 컨테이너 환경 변수가 아니라 Docker 데몬의 프록시를 설정해야 합니다.
- 호스트는 되지만 컨테이너 로그가 비어 있음: Docker 브리지 패킷이 Clash TUN 또는 redir 포트에 도달하지 않는 상태입니다.
- 연결 후 내부 서비스가 실패:
NO_PROXY, LAN 예외, fake-ip-filter에 내부 도메인을 추가합니다.
가장 안정적인 순서는 먼저 환경 변수 프록시로 애플리케이션을 검증하고, 이후 필요한 범위에만 TUN 또는 iptables를 적용하는 것입니다. 모든 트래픽을 무조건 프록시로 보내면 속도와 보안 정책이 복잡해질 수 있습니다. 개발 도구 도메인, 사내 서비스, 로컬 네트워크의 경계를 명확히 나누고 Clash 로그와 Docker 로그를 함께 관찰하면 불필요한 시행착오를 크게 줄일 수 있습니다.