이 글이 필요한 Docker 네트워크 구성

Docker 컨테이너마다 프록시 환경변수를 넣지 않고 외부 트래픽을 Clash로 통합하고 싶다면, 단순한 HTTP_PROXY 설정만으로는 부족할 수 있습니다. 개발 서버, CI 러너, NAS, 미니 PC, 홈랩의 여러 컨테이너가 동시에 인터넷에 연결되면 이미지 다운로드·패키지 설치·API 호출·웹훅 통신이 서로 다른 경로로 빠져나가기 쉽습니다. 어떤 컨테이너는 프록시를 타고, 어떤 컨테이너는 호스트의 기본 라우팅을 사용하면 장애가 간헐적으로 보이고 로그만으로 원인을 찾기도 어렵습니다.

이 글에서는 Mihomo 또는 Clash Meta 계열 코어를 기준으로 Docker bridge 네트워크의 외부 연결을 투명 프록시처럼 처리하는 구조를 설명합니다. 핵심은 컨테이너 안의 애플리케이션을 일일이 수정하는 것이 아니라, 호스트의 포워딩·NAT·라우팅 계층에서 트래픽을 Clash의 TUN 또는 리다이렉트 포트로 넘기는 것입니다. 동시에 모든 트래픽을 무조건 프록시로 보내지 않고, 사설망·Docker 내부 통신·로컬 레지스트리는 직접 연결로 남기는 분할 라우팅을 구성합니다.

투명 프록시는 편리하지만 DNS와 권한을 함께 다뤄야 합니다. DNS 질의가 호스트 기본 DNS로 새거나, Docker가 생성한 사설 주소까지 프록시로 보내면 연결 지연과 예기치 않은 루프가 발생합니다. 따라서 먼저 네트워크 구조를 그린 뒤, 적용 범위와 제외 대상을 정하고, 마지막에 규칙 제공자와 장애 진단 절차를 붙이는 순서가 안전합니다.

Docker와 Clash 투명 프록시의 기본 토폴로지

가장 관리하기 쉬운 구조는 Clash를 Docker 호스트에서 실행하고, 컨테이너는 일반적인 bridge 네트워크를 사용하는 방식입니다. 컨테이너의 기본 게이트웨이는 Docker bridge가 되고, 호스트의 iptables 또는 nftables 규칙이 외부 목적지로 향하는 TCP·UDP 패킷을 Clash의 투명 포트로 리다이렉트합니다. 애플리케이션 입장에서는 프록시 주소를 알 필요가 없으므로, 프록시를 지원하지 않는 바이너리나 오래된 개발 도구에도 적용할 수 있습니다.

구성 요소 주요 역할 확인할 항목
Docker bridge 컨테이너의 기본 가상 네트워크와 게이트웨이 제공 bridge 서브넷, 기본 게이트웨이, 컨테이너별 고정 주소
호스트 커널 IP forwarding, NAT, 리다이렉트 규칙 처리 포워딩 활성화, 방화벽 체인, 재부팅 후 규칙 복원
Clash 코어 DNS 처리, 도메인 규칙 매칭, 프록시 그룹 선택 TUN 또는 redir-port, auto-route, sniffing, 로그
규칙 제공자 개발 도구·지역 서비스·사설망 도메인을 그룹으로 분류 provider 갱신 주기, 형식, 규칙 우선순위

Docker를 network_mode: host로 실행하는 컨테이너는 일반 bridge 컨테이너와 관찰 지점이 다릅니다. 이 모드에서는 컨테이너가 호스트 네트워크 네임스페이스를 공유하므로 bridge 체인에만 넣은 규칙이 예상대로 동작하지 않을 수 있습니다. 반대로 macvlan을 사용하면 컨테이너가 LAN의 독립 장치처럼 보이지만, 호스트와의 통신 제한과 별도의 라우팅 문제가 생깁니다. 처음 구성할 때는 bridge 네트워크 한 개로 범위를 좁히고, 성공한 뒤 필요한 서비스만 다른 모드로 옮기는 편이 좋습니다.

또한 컨테이너 간 통신은 외부 프록시와 분리해야 합니다. PostgreSQL, Redis, 내부 API처럼 Docker 네트워크 안에서만 접근하는 서비스의 패킷까지 투명 프록시로 보내면 DNS 이름 해석이 꼬이거나, 프록시 그룹 변경에 따라 내부 서비스가 일시적으로 끊길 수 있습니다. 172.16.0.0/12, 192.168.0.0/16, 10.0.0.0/8, Docker bridge 대역은 기본적으로 직접 연결 예외에 포함하는 것이 일반적입니다.

Clash 기본 설정: TUN, DNS, 혼합 포트

Clash 설정을 시작하기 전에 투명 처리 방식을 선택해야 합니다. TUN 모드는 가상 네트워크 인터페이스와 라우팅 테이블을 사용해 TCP뿐 아니라 일부 UDP 흐름까지 넓게 다룰 수 있습니다. 반면 redir-port는 리다이렉트된 TCP 연결을 처리하는 데 단순하고 가볍지만, UDP와 애플리케이션별 특수 프로토콜은 별도의 설계가 필요합니다. 개발 서버의 HTTPS, Git, 패키지 레지스트리, REST API가 중심이면 redir 방식으로 시작할 수 있고, DNS와 UDP까지 한 정책으로 묶어야 한다면 TUN이 더 적합합니다.

예시 설정은 코어와 배포판에 따라 키 이름이 달라질 수 있으므로, 사용 중인 Mihomo 버전의 문서를 함께 확인해야 합니다. 아래는 개념을 보여 주는 최소 예시입니다.

mixed-port: 7890
redir-port: 7892
tproxy-port: 7893

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  strict-route: true

dns:
  enable: true
  enhanced-mode: fake-ip
  listen: 0.0.0.0:1053
  nameserver:
    - https://dns.cloudflare.com/dns-query
    - https://1.1.1.1/dns-query
  fallback:
    - https://dns.google/dns-query
  fake-ip-filter:
    - '*.lan'
    - '*.local'
    - 'localhost.ptlogin2.qq.com'

mixed-port는 환경변수를 사용하는 애플리케이션이나 테스트 도구가 직접 연결할 수 있는 포트입니다. 투명 프록시를 적용하더라도 문제 재현과 비교 테스트를 위해 남겨 두는 것이 좋습니다. redir-porttproxy-port는 호스트 방화벽 규칙에서 실제로 참조하는 포트와 일치해야 합니다. 포트 번호만 바꾸고 리다이렉트 규칙을 갱신하지 않으면 Clash 프로세스는 정상인데 패킷은 그대로 직접 연결됩니다.

DNS는 특히 신중하게 설정합니다. Docker의 기본 DNS는 보통 호스트 또는 Docker 내부 DNS 프록시를 가리키며, 컨테이너가 도메인을 먼저 해석한 뒤 IP 주소로 접속하면 Clash의 도메인 기반 규칙이 약해질 수 있습니다. TUN의 DNS 하이재킹 기능이나 Clash가 제공하는 DNS 리스너를 사용하되, 내부 도메인과 Docker 서비스 이름은 예외로 둡니다. fake-ip을 사용할 때는 사내 도메인, 프린터, NAS, *.lan 같은 이름을 fake-ip 대상에서 제외해야 실제 주소를 얻을 수 있습니다.

실습: Docker 외부 트래픽을 선택적으로 Clash에 태우기

이제 실제 적용 순서를 정리해 보겠습니다. 먼저 현재 네트워크 정보를 기록합니다. 호스트에서 docker network inspect bridge를 실행해 서브넷과 게이트웨이를 확인하고, 컨테이너 안에서 ip routecat /etc/resolv.conf를 확인합니다. 이 두 결과를 저장해 두면 적용 전후에 경로와 DNS가 어떻게 달라졌는지 비교하기 쉽습니다.

  1. Clash 리스너를 호스트에 열기: Docker 컨테이너가 접근할 수 있도록 Clash가 로컬 루프백만 듣지 않게 합니다. 단, 관리 API 포트까지 0.0.0.0에 노출하지 말고, 투명 포트와 관리 포트를 분리합니다.
  2. 포워딩 활성화: 호스트에서 IPv4 forwarding을 켜고, Docker bridge에서 외부 인터페이스로 나가는 패킷이 정상적으로 전달되는지 확인합니다. 방화벽 기본 정책이 DROP이면 포워딩 허용 체인을 별도로 추가해야 합니다.
  3. 내부망을 먼저 예외 처리: Docker bridge 대역, 호스트 자신, LAN, 사내 레지스트리, 데이터베이스 주소를 DIRECT 대상으로 고정합니다. 예외를 나중에 추가하면 이미 프록시를 거친 내부 연결 때문에 원인 파악이 어려워집니다.
  4. 외부 TCP를 리다이렉트: 컨테이너에서 외부로 나가는 새 연결만 Clash의 redir 또는 TUN 경로로 보냅니다. Clash 자신의 리스너 포트와 이미 프록시 처리된 연결은 다시 리다이렉트하지 않도록 UID, 목적지 포트, 인터페이스 조건을 둡니다.
  5. DNS 경로 정렬: 컨테이너의 DNS 요청이 호스트의 Clash DNS 리스너로 가는지 확인합니다. 내부 이름은 Docker DNS가 처리하고, 외부 이름만 Clash의 원격 DNS 정책으로 넘기는 구성이 안전합니다.
  6. 한 컨테이너로 검증: 전체 Compose 스택을 한 번에 전환하지 말고, 테스트용 컨테이너 하나에 먼저 적용합니다. 외부 HTTPS, 패키지 저장소, 내부 API, 사설망 주소를 각각 확인한 뒤 범위를 넓힙니다.

Compose 단계에서는 모든 서비스에 HTTP_PROXY를 강제로 주입하는 대신, 필요할 때만 테스트용 환경변수를 사용할 수 있습니다. 투명 라우팅이 올바르게 작동하는지 확인하려면 같은 컨테이너에서 환경변수를 제거한 상태로 요청해야 합니다. 그렇지 않으면 애플리케이션 레벨 프록시와 네트워크 레벨 프록시가 동시에 적용되는 이중 프록시가 되어, 인증 실패·지연 증가·예상하지 못한 DNS 해석이 발생할 수 있습니다.

다만 투명 프록시가 모든 종류의 트래픽을 자동으로 해결하는 것은 아닙니다. 애플리케이션이 자체적으로 프록시를 무시하는 QUIC, 커스텀 UDP 프로토콜, 인증서 고정(pin) 기능을 사용하면 별도 대응이 필요합니다. 이런 서비스는 먼저 직접 연결과 mixed-port 연결을 비교하고, 정말 필요한 경우에만 TUN의 UDP 처리와 sniffing을 활성화합니다. 기능을 많이 켜는 것보다 문제의 프로토콜을 확인한 뒤 최소 옵션을 선택하는 편이 안정적입니다.

규칙 제공자와 개발 도구 분할 라우팅

개발 환경에서는 모든 외부 주소를 같은 프록시 그룹으로 보내는 것보다 목적별로 나누는 것이 좋습니다. Git 저장소, 컨테이너 레지스트리, 언어별 패키지 저장소, AI API, 일반 웹 트래픽의 지연과 접근 정책이 서로 다르기 때문입니다. 규칙 제공자를 사용하면 도메인 목록을 프로필 본문에 길게 복사하지 않고, 업데이트 주기와 그룹을 분리해 관리할 수 있습니다.

rule-providers:
  dev-platforms:
    type: http
    behavior: domain
    format: yaml
    url: https://example.invalid/rules/dev-platforms.yaml
    path: ./ruleset/dev-platforms.yaml
    interval: 86400
  container-registries:
    type: http
    behavior: domain
    format: yaml
    url: https://example.invalid/rules/container-registries.yaml
    path: ./ruleset/container-registries.yaml
    interval: 86400

rules:
  - RULE-SET,container-registries,DEV_PROXY
  - RULE-SET,dev-platforms,DEV_PROXY
  - DOMAIN-SUFFIX,company.internal,DIRECT
  - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - MATCH,DIRECT

위 주소는 형식 설명을 위한 예시이므로 실제로는 신뢰할 수 있는 규칙 제공자 URL로 교체해야 합니다. 제공자 파일이 YAML인지 text인지, 도메인 규칙인지 IP 규칙인지가 맞지 않으면 업데이트는 성공한 것처럼 보여도 매칭이 일어나지 않을 수 있습니다. Clash 로그에서 provider 갱신 시 HTTP 상태 코드와 파싱 오류를 확인하고, 갱신된 규칙 수가 0으로 표시되면 파일 형식부터 점검하세요.

규칙은 위에서 아래로 평가되므로 우선순위가 중요합니다. MATCH,DIRECT를 너무 앞에 두면 이후의 개발 도메인 규칙은 도달하지 않습니다. 반대로 광범위한 DOMAIN-SUFFIX를 사설 도메인 예외보다 앞에 놓으면 내부 서비스가 프록시 그룹으로 들어갑니다. 보안과 운영 편의성을 위해 내부 네트워크 예외, 특정 업무 서비스, 개발 도구, 일반 트래픽 순서로 배치하고, 마지막에 기본 정책을 둡니다.

개발 도구는 도메인 하나만 호출하지 않습니다. Git 클라이언트는 저장소 호스트와 인증 호스트, 컨테이너 런타임은 레지스트리와 토큰 발급 호스트, 패키지 관리자는 미러와 서명 서버를 각각 사용할 수 있습니다. 첫 요청이 성공해도 토큰 교환이나 리다이렉트 단계에서 다른 호스트가 DIRECT로 빠질 수 있으므로, Clash 연결 로그에서 실제로 등장한 호스트를 시간순으로 기록해야 합니다. 규칙을 DOMAIN-KEYWORD 하나로 넓게 잡기보다는 확인된 접미사를 명시하는 방식이 다른 서비스에 미치는 영향을 줄입니다.

DNS 누수 방지와 선택적 우회

DNS 누수는 컨테이너가 외부 도메인을 직접 해석하는 상황만을 뜻하지 않습니다. Clash는 프록시 출구를 사용하지만 DNS는 호스트 ISP로 나가거나, 반대로 DNS는 원격으로 보내면서 내부 서비스 이름까지 외부로 질의하는 경우도 포함됩니다. 전자는 지역별 응답 차이와 규칙 오판을 만들고, 후자는 사내 호스트명 노출과 연결 실패를 유발합니다.

외부 도메인은 Clash DNS의 원격 DoH·DoT 정책을 사용하고, 내부 도메인은 명시적인 nameserver 또는 Docker 내부 DNS로 보냅니다. fake-ip-filter에는 *.docker, *.internal, *.lan 등 실제 환경의 suffix를 넣되, 너무 넓은 패턴을 넣어 외부 서비스까지 제외하지 않도록 합니다. 컨테이너 안에서 getent hosts service-name가 계속 성공하는지와 외부 도메인이 동일한 응답 경로를 쓰는지는 따로 검증해야 합니다.

선택적 우회는 성능을 위한 기능이기도 합니다. 사내 GitLab, 로컬 Docker Registry, LAN에 있는 모니터링 서버를 프록시로 보내면 불필요한 왕복이 늘고, IP 허용 목록이 있는 서비스는 출구 IP가 바뀌어 거부될 수 있습니다. 이런 주소는 DOMAIN-SUFFIXIP-CIDR를 함께 사용해 직접 연결로 고정합니다. 단, 공용 클라우드 서비스의 IP 대역은 자주 바뀌므로 고정 IP 예외를 장기간 유지하지 말고, 도메인 규칙이나 공식 제공자 목록을 우선 사용하세요.

연결 실패를 단계별로 진단하는 방법

첫 번째로 컨테이너 내부의 기본 경로를 확인합니다. ip route get 1.1.1.1 결과가 Docker 게이트웨이를 가리키는지, 인터페이스가 예상한 bridge인지 봅니다. 두 번째로 DNS를 확인합니다. getent hosts example.com 또는 애플리케이션이 사용하는 DNS 도구를 실행해 응답이 오는지 확인하고, Clash 로그에 같은 질의가 기록되는지 비교합니다.

세 번째는 프록시 매칭입니다. Clash의 연결 로그에서 대상 호스트, 목적지 포트, 선택된规则组, 최종链路를 확인합니다. 호스트가 보이지 않으면 트래픽이 Clash까지 도달하지 않은 것이고, 보이지만 DIRECT라면 규칙 우선순위나 provider 내용을 점검해야 합니다. REJECT라면 광고·악성 도메인 제공자가 API나 패키지 저장소를 잘못 포함했는지 확인합니다.

네 번째는 TLS와 MTU입니다. DNS와 규칙이 정상인데 대용량 이미지 풀이나 장시간 스트리밍만 실패한다면, 프록시 체인의 MTU·HTTP/2·QUIC 처리를 의심합니다. 작은 HTTPS 요청과 큰 응답을 나누어 시험하고, 같은 목적지를 mixed-port로 직접 지정했을 때 결과가 달라지는지 비교합니다. 컨테이너 애플리케이션의 인증서 저장소가 오래된 경우에는 네트워크 문제가 아니라 CA 번들 문제일 수 있으므로, curl의 인증서 오류와 Clash의 연결 종료 로그를 구분해서 읽어야 합니다.

마지막으로 재시작 테스트를 합니다. Clash 재시작, Docker 네트워크 재생성, 호스트 재부팅 뒤에도 forwarding·방화벽·라우팅 규칙이 동일하게 복원되는지 확인합니다. 수동으로 실행한 명령에만 의존하면 업데이트나 장애 복구 후 투명 라우팅이 사라질 수 있습니다. systemd, Docker Compose, 방화벽 영속화 도구 중 하나를 정해 적용 순서를 문서화하고, 설정 파일과 규칙 제공자 버전을 함께 백업하세요.

운영 보안과 유지 관리 체크리스트

Clash의 관리 API와 mixed-port를 인터넷에 그대로 노출하지 마세요. Docker 호스트의 방화벽에서 LAN과 필요한 컨테이너 대역만 허용하고, 관리 API에는 강한 비밀번호를 설정합니다. 컨테이너가 호스트 네트워크를 직접 조작해야 하는 구성은 NET_ADMIN과 장치 권한을 요구할 수 있으므로, 필요한 권한만 부여하고 일반 애플리케이션 컨테이너와 분리하는 것이 좋습니다. 권한이 큰 Clash 컨테이너의 이미지는 고정된 버전과 검증된 레지스트리를 사용하고, 자동 업데이트 전에 설정 키 변경 여부를 확인하세요.

규칙 제공자는 편리하지만 외부 파일이 라우팅 정책을 바꿀 수 있다는 뜻이기도 합니다. 제공자 URL의 TLS 인증서와 도메인을 확인하고, 갱신 후 규칙 수·파일 해시·주요 도메인의 매칭 결과를 점검합니다. 업무 환경에서는 무조건 최신 목록을 즉시 적용하기보다 스테이징 프로필에서 하루 정도 검증한 뒤 운영 프로필에 반영하는 방식이 안전합니다. 특히 no-resolve가 필요한 IP 규칙과 DNS 기반 도메인 규칙을 혼동하면, 예상보다 많은 DNS 질의가 발생할 수 있습니다.

단순한 호스트 환경변수 방식은 작은 프로젝트에서 빠르고 명확하지만, 컨테이너 수가 늘어날수록 누락·비밀값 노출·서비스별 설정 차이가 커집니다. 반대로 모든 네트워크를 강제로 TUN에 넣으면 예외 관리와 권한 부담이 커질 수 있습니다. 따라서 개발 도구와 홈랩처럼 트래픽 종류가 다양한 환경에서는 Docker bridge 외부 트래픽만 Clash에 맡기고, 내부망은 직접 연결로 남기는 분할 구조가 균형 잡힌 선택입니다. 다른 프록시 도구보다 Clash는 TUN, DNS 정책, 규칙 제공자, 연결 로그를 한 프로필에서 함께 조정할 수 있어 컨테이너별 환경변수 없이도 경로를 눈으로 확인하고 재현하기 쉽습니다. 복잡한 수동 포워딩 스크립트에 의존하는 대신 이런 정책을 관리 가능한 프로필로 정리하고 싶다면, 각 플랫폼에 맞는 Clash 클라이언트를 내려받아 환경에 맞는 구성을 시작해 보세요.

지금 Clash를 무료로 다운로드하고 자유로운 인터넷 경험을