개발 환경 · · 약 14분 소요

Docker 컨테이너가 호스트 Clash를 타고 나가기: HTTP_PROXY 환경 변수와 리눅스 게이트웨이 방식 실측 (2026)

로컬 개발이나 CI 파이프라인에서 Docker 컨테이너가 git clone·npm install·pip로 밖으로 나가야 할 때, 컨테이너마다 Clash를 또 설치하는 대신 이미 떠 있는 호스트 Clash 한 벌만 쓰고 싶은 경우가 많습니다. 이 글은 HTTP_PROXY 계열 환경 변수로 mixed-port에 붙는 방법과, 리눅스에서 게이트웨이·호스트 네트워크를 염두에 둔 대안을 나란히 두고, 무엇이 잘 되고 무엇이 애매한지 실측 관점으로 정리합니다. Ubuntu에 Clash Meta를 올린 뒤 데스크톱에서 Docker를 같이 쓰는 흐름과 맞물립니다.

1. 왜 컨테이너 안에 Clash를 또 두지 않을까

컨테이너마다 프록시 클라이언트를 넣으면 이미지가 무거워지고, 구독 URL·프로필 갱신·로그 위치까지 컨테이너 생명주기에 묶입니다. 팀 단위로는 보안 정책상 「한 대의 호스트에서만 출구를 관리한다」는 요구도 흔합니다. 그래서 실무에서는 호스트 OS에서 Clash(또는 mihomo)를 띄우고, 컨테이너는 그 소켓이나 HTTP 프록시 엔드포인트만 바라보게 두는 패턴이 자주 쓰입니다. 문제는 Docker가 기본적으로 브리지 네트워크를 쓰기 때문에, 컨테이너 입장에서 「호스트의 127.0.0.1」이 곧바로 호스트의 Clash가 아니라는 점입니다. 그 간극을 메우는 것이 이 글의 중심입니다.

검색으로 들어오는 분들은 「docker 프록시」「컨테이너 clash」처럼 짧은 키워드로도 찾습니다. 핵심은 컨테이너의 기본 라우팅애플리케이션이 프록시 환경 변수를 존중하는지를 동시에 보는 것입니다. 둘 중 하나만 맞아도 증상은 반쯤 해결되지만, 나머지 하나가 어긋나면 「curl만 되고 npm은 안 된다」처럼 보이기 쉽습니다.

2. 호스트 쪽 전제: mixed-port·allow-lan·바인드 주소

Clash 계열에서 mixed-port는 한 포트에 HTTP CONNECT와 SOCKS를 함께 받는 설정이 흔합니다. 컨테이너가 HTTP_PROXY=http://<호스트>:<포트> 형태로 붙을 때는 이 포트가 실제로 리슨 중이어야 하고, LAN·브리지 대역에서의 접속을 막지 않아야 합니다. GUI 클라이언트라면 「Allow LAN」에 해당하는 옵션을 켜 두는 식으로 정리됩니다. 바인드가 127.0.0.1에만 걸려 있으면 호스트 밖(브리지 네트워크의 컨테이너)에서 오는 SYN이 도달하지 않습니다. 반대로 0.0.0.0에 열어두면 편하지만, 사내망에서는 방화벽·ACL과 함께 범위를 제한하는 편이 안전합니다.

한 줄 정리

컨테이너가 붙을 수 있는 호스트 측 IP + mixed-port가 열려 있어야 하며, Clash 규칙·노드는 호스트에서 그대로 적용됩니다.

3. 방식 A: HTTP_PROXY·HTTPS_PROXY·ALL_PROXY 환경 변수

가장 단순한 방법은 빌드·런타임에 표준 환경 변수를 주입하는 것입니다. HTTP_PROXYHTTPS_PROXY는 대부분의 HTTP 클라이언트 스택이 읽습니다. ALL_PROXY는 일부 도구가 SOCKS까지 한 번에 쓰도록 붙이기도 합니다. 값은 보통 http://호스트IP:포트 형식이며, 여기서 「호스트 IP」가 플랫폼마다 달라집니다.

Docker Desktop(Mac·Windows)에서는 특수 호스트 이름 host.docker.internal이 문서화되어 있어, 컨테이너에서 호스트로의 접속에 자주 씁니다. 예를 들어 호스트 Clash의 mixed-port가 7890이면 http://host.docker.internal:7890처럼 적습니다. 리눅스 네이티브 Docker에서는 최근 버전에서 같은 이름이 지원되는 경우가 있으나, 배포판·데몬 설정에 따라 172.17.0.1(docker0 게이트웨이)이나 실제 브리지 주소를 쓰는 팀도 많습니다. 확실히 하려면 호스트에서 ip -4 addr show docker0 등으로 게이트웨이를 확인하고, 컨테이너 안에서 nc -vz <IP> <포트>로 연결성부터 보는 것이 좋습니다.

# Example: run with proxy env (adjust host and port)
docker run --rm -it \
  -e HTTP_PROXY=http://host.docker.internal:7890 \
  -e HTTPS_PROXY=http://host.docker.internal:7890 \
  -e NO_PROXY=localhost,127.0.0.1,.internal \
  alpine sh -c "wget -qO- https://example.com | head"

빌드 단계에서 레지스트리나 Git이 필요하면 docker build --build-arg HTTP_PROXY=...를 병행해야 합니다. BuildKit을 쓰면 전달 방식이 조금 달라질 수 있으니, CI 로그에 프록시 관련 경고가 없는지 함께 확인하세요.

4. Docker Compose·CI에서 넣는 패턴

Compose에서는 서비스 단위 environment 블록이나 루트의 env_file로 동일한 값을 공유하기 쉽습니다. 개발자마다 호스트 IP가 다르면 .envPROXY_HOST=...만 두고 조합하는 방식이 실수를 줄입니다. CI 러너가 리눅스 VM이라면 host.docker.internal이 없을 수 있으므로, 그때는 러너 문서에 맞는 게이트웨이 IP나 sidecar 프록시를 정하는 편이 낫습니다.

services:
  app:
    image: node:20-bookworm
    environment:
      HTTP_PROXY: ${HTTP_PROXY:-http://172.17.0.1:7890}
      HTTPS_PROXY: ${HTTPS_PROXY:-http://172.17.0.1:7890}
      NO_PROXY: localhost,127.0.0.1,registry.internal

팀에서 구독·프로필을 표준화해 두었다면, 컨테이너 쪽은 환경 변수만 맞추면 호스트 Clash의 정책 그룹·규칙을 그대로 타게 됩니다. 규칙 우선순위 개념은 고급 라우팅 가이드와 함께 보면 디버깅이 빨라집니다.

5. NO_PROXY로 내부 레지스트리·메쉬 제외

사내 Nexus·Harbor·GitLab 같은 내부 호스트까지 프록시로내면 지연이 늘거나 TLS 상호 인증이 깨질 수 있습니다. NO_PROXY에 도메인 접미·IP 대역을 넣어 직접 경로로 빼 주는 것이 일반적입니다. 쉼표로 나열하고, 앞에 점을 붙여 접미 일치를 쓰는 관례(.corp.example.com)도 함께 기억해 두면 운영 중 수정이 줄어듭니다.

6. 환경 변수 방식의 한계: 누가 안 따를까

Go·Node·많은 CLI는 HTTP_PROXY를 잘 따르지만, 특정 바이너리는 무시하거나 자체 설정 파일만 봅니다. DNS 조회가 프록시 밖에서 먼저 일어나는 경우도 있어, fake-ip·스플릿 DNS를 호스트 Clash에서 쓰는 환경이면 컨테이너의 리졸버 설정까지 맞춰야 하는 순간이 생깁니다. 또한 환경 변수는 주로 HTTP(S) 계열에 가깝고, 임의의 TCP·UDP 포트를 통째로 터널링하는 앱에는 부족할 수 있습니다. 그럴 때는 TUN을 호스트에서 쓰거나, 아래 게이트웨이·호스트 네트워크 쪽을 검토하게 됩니다.

개발용으로는 IDE·npm·API를 도메인 규칙으로 묶어 둔 프로필을 호스트에 그대로 두고, 컨테이너만 프록시로 붙이는 구성이 자연스럽게 이어집니다.

7. 방식 B: 리눅스 게이트웨이·호스트 네트워크와 비교

「컨테이너의 기본 게이트웨이를 호스트 쪽 프록시/라우터로 보낸다」는 접근은 네트워크 관리자 친화적이지만, 단순히 Clash만으로 끝내기보다는 호스트 iptables·nftables·라우팅 테이블과 함께 설계하는 경우가 많습니다. 반면 docker run --network host는 컨테이너가 호스트의 네임스페이스를 공유하므로, 호스트에서 이미 TUN·Clash로 전역 라우팅이 잡혀 있다면 애플리케이션이 환경 변수 없이도 같은 경로를 탈 수 있습니다. 대가로 포트 충돌·격리 약화가 있어 CI·멀티 테넌트에는 신중해야 합니다.

실무에서는 먼저 방식 A로 git·npm·pip·apt가 요구하는 시나리오를 통과시키고, 특정 데몬만 예외라면 그 프로세스에 한해 호스트 네트워크나 별도 sidecar를 검토하는 순서가 부담이 적습니다. 리눅스 서버에 Clash를 상주시키는 절차는 Ubuntu·systemd 글의 흐름과 직결됩니다.

보안: mixed-port를 LAN 전체에 열면 같은 스위치에 붙은 기기도 접속할 수 있습니다. 개발 전용 Wi-Fi·로컬 방화벽으로 노출 범위를 제한하세요.

8. 실측 점검 순서

권장 순서는 다음과 같습니다. 첫째, 컨테이너 안에서 호스트 후보 IP와 포트에 대해 TCP 연결이 되는지 확인합니다. 둘째, curl -I로 외부 HTTPS 한 번, 내부 레지스트리 한 번을 나란히 찍어 NO_PROXY가 기대대로인지 봅니다. 셋째, 실패 시 호스트 Clash 로그에서 해당 연결이 어떤 규칙·아웃바운드로 나갔는지 확인합니다. 넷째, 빌드 단계와 런타임 단계 중 어디서 끊기는지 나누어 CI 로그를 재생합니다.

환경 변수가 먹었는지

env | grep -i proxy로 주입 여부를 보고, 동일 이미지에서 프록시를 뺀 대조 실험을 한 번 실행해 보세요.

DNS 이슈인지

IP로는 되는데 이름만 실패하면 리졸버 경로를 의심합니다. 호스트와 컨테이너의 /etc/resolv.conf 차이를 비교해 보세요.

정리하면, Docker호스트 Clash를 타고 나가게 하는 가장 현실적인 첫 수는 HTTP_PROXY 계열과 올바른 호스트 도달 주소(host.docker.internal 또는 브리지 게이트웨이)를 맞추는 것입니다. 여기에 NO_PROXY로 내부 트래픽을 분리하면 레지스트리·메쉬까지 한 번에 건드리지 않습니다. 환경 변수만으로 부족한 프로세스가 보이면 호스트 네트워크나 호스트 측 라우팅을 단계적으로 검토하면 됩니다. 같은 목적의 상용 VPN 앱들에 비해, Clash 쪽은 규칙·프로필을 파일로 관리하고 로그로 원인을 좁히기 좋아 개발자 워크플로에 잘 맞는 편입니다.

Clash를 무료로 내려받아 호스트에서 mixed-port·LAN 허용·프로필을 정리한 뒤, 컨테이너 환경 변수만 맞춰 Git·npm·pip 경로를 다시 점검해 보세요.

주제 관련도가 높은 읽을거리 — 같은 카테고리의 Clash 실전 가이드.