1. 왜 컨테이너만 프록시가 빗나갈까
Docker Desktop for macOS는 경량 VM과 가상 이더넷 위에서 Linux 컨테이너를 돌립니다. 호스트 macOS에서 Clash가 시스템 프록시를 켠다고 해서, 그 설정이 컨테이너 내부의 모든 프로세스에 자동 전파되지는 않습니다. 컨테이너 셸은 대개 비어 있는 환경에서 시작하므로 http_proxy가 없으면 레지스트리·패키지 저장소로 직접 나가려 하고, 그 경로가 회사망이나 지역 제한에 걸리면 타임아웃으로 끝납니다.
또한 컨테이너 안에서 127.0.0.1:7890처럼 적으면 그건 컨테이너 자신의 루프백을 가리키므로 호스트 Clash와 연결되지 않습니다. 해결책은 호스트 쪽 프록시가 리슨하는 주소를 컨테이너에서 도달 가능한 호스트 IP로 바꾸는 것인데, Docker가 제공하는 표준 별칭이 host.docker.internal입니다. LAN에서 다른 기기와 공유할 때 쓰는 allow-lan 개념은 LAN 프록시 가이드와 같은 맥락으로, 호스트가 모든 인터페이스에서 mixed-port를 받아야 컨테이너에서도 연결이 열립니다.
2. macOS Clash 측 준비: mixed-port·allow-lan·방화벽
프로필에서 mixed-port(예: 7890)가 HTTP와 SOCKS를 한 포트에서 받도록 고정해 두세요. 이후 모든 예시에서 포트 숫자는 본인 환경에 맞게 바꿉니다. allow-lan을 true로 두면 데몬이 루프백뿐 아니라 가상 브리지 너머에서 오는 연결도 받을 수 있어, Docker 네트워크에서 들어오는 클라이언트가 거절당하지 않습니다. GUI 사용자는 Clash Verge Rev 입문 가이드에서 프로필 적용과 시스템 프록시 토글을 익힌 뒤, 설정 편집기에서 위 두 항목을 확인하면 됩니다.
macOS 방화벽이나 서드파티 보안 제품이 인바운드 연결을 막으면 컨테이너에서 host.docker.internal:7890으로 붙을 때 즉시 끊깁니다. “들어오는 연결 허용” 목록에 Clash 앱을 넣거나, 테스트 단계에서만 방화벽 규칙을 완화한 뒤 성공 여부를 확인하세요. 카페 등 공용 네트워크에서는 allow-lan으로 노출된 포트가 의도치 않게 열릴 수 있으니 사용 후 끄는 습관이 안전합니다.
3. host.docker.internal 과 프록시 URL 형태
Docker Desktop은 Linux 컨테이너에서 호스트 머신을 가리키는 DNS 이름으로 host.docker.internal을 제공합니다. HTTP CONNECT를 mixed-port로 보낼 때는 보통 다음 형태를 씁니다.
http://host.docker.internal:7890
SOCKS가 필요한 도구는 socks5h://host.docker.internal:7890처럼 적되, h 접미사는 DNS를 프록시 측에서 해석하게 하여 누수를 줄입니다. 일부 오래된 Compose 파일은 extra_hosts로 같은 이름을 수동 매핑하기도 하지만, 최신 Docker Desktop에서는 기본 제공되는 경우가 많아 우선 생략하고 동작을 확인한 뒤 필요할 때만 추가하는 편이 단순합니다.
4. Docker Desktop 앱 설정: 수동 프록시(Manual proxy)
엔진이 레지스트리에서 이미지를 받을 때와 빌드 초기 단계에서 쓰는 프록시는 호스트 앱 설정과 일치시키는 것이 재현성이 높습니다. Docker Desktop → Settings → Resources → Proxies에서 “Manual proxy configuration”을 켜고, Web Server(HTTP)와 Secure Web Server(HTTPS)에 동일하게 넣습니다.
http://host.docker.internal:7890
HTTPS 필드에도 같은 URL을 넣어도 됩니다. Clash mixed-port는 일반적으로 CONNECT를 같은 포트에서 처리합니다. 적용 후 Docker를 재시작하라는 안내가 나오면 따르고, docker pull hello-world로 먼저 검증합니다. 여기서 실패하면 컨테이너 내부가 아니라 데몬·VM 측 출구가 막힌 것이므로 호스트에서 curl -I로 프록시 자체가 살아 있는지, Clash 로그에 Docker 관련 소스가 찍히는지부터 봅니다.
5. 단발 실행: docker run 환경 변수 블록
실행 중인 컨테이너에만 프록시를 주고 싶다면 -e로 대문자·소문자를 함께 넣는 패턴이 호환성이 좋습니다.
docker run --rm -it \
-e HTTP_PROXY=http://host.docker.internal:7890 \
-e HTTPS_PROXY=http://host.docker.internal:7890 \
-e http_proxy=http://host.docker.internal:7890 \
-e https_proxy=http://host.docker.internal:7890 \
-e NO_PROXY=localhost,127.0.0.1,::1 \
-e no_proxy=localhost,127.0.0.1,::1 \
debian:stable-slim bash
컨테이너 안에서 apt update나 curl를 시험해 보세요. NO_PROXY에 사내 레지스트리나 로컬 서비스 호스트명을 더하면 불필요한 프록시 우회를 줄일 수 있습니다. Git·npm·pip 등은 각각 추가 설정이 필요할 수 있으나, 많은 경우 위 환경 변수만으로도 레지스트리 접속이 살아납니다.
6. Docker Compose: environment와 build.args
서비스 단위로 동일한 블록을 반복하지 않으려면 Compose 파일의 environment에 한 번만 정의합니다.
services:
app:
image: node:20-bookworm
environment:
HTTP_PROXY: http://host.docker.internal:7890
HTTPS_PROXY: http://host.docker.internal:7890
NO_PROXY: localhost,127.0.0.1,::1
extra_hosts:
- "host.docker.internal:host-gateway"
extra_hosts의 host-gateway는 Docker가 자동으로 호스트 게이트웨이 IP로 풀어 주는 특수 값입니다. 일부 환경에서 기본 DNS만으로는 host.docker.internal이 비어 있을 때 안전망으로 쓸 수 있습니다. docker compose build 단계에도 프록시를 넘기려면 같은 키를 build.args에 복제하거나, 셸에서 DOCKER_BUILDKIT=1과 함께 빌드 전에 호스트에 HTTP_PROXY를 export하는 방식을 병행합니다. BuildKit은 ARG HTTP_PROXY를 Dockerfile 상단에서 선언해야 빌드 컨텍스트로 전달되는 경우가 많습니다.
7. Dockerfile 예시: ARG로 빌드 시 프록시 전달
베이스 이미지를 받거나 빌드 중 apt-get을 실행할 때, 빌드 인자 없이는 호스트 프록시를 모릅니다. 최소 예는 다음과 같습니다.
ARG HTTP_PROXY
ARG HTTPS_PROXY
ENV HTTP_PROXY=$HTTP_PROXY HTTPS_PROXY=$HTTPS_PROXY
Compose에서는 build.args에 위 ARG 이름과 URL을 맞춰 주면 됩니다. 빌드가 끝난 런타임 이미지에 프록시 URL을 남기고 싶지 않다면 다단계 빌드에서 마지막 스테이지에는 ENV를 넣지 않는 방식으로 정리할 수 있습니다. 조직 정책상 프록시 인증·사내 CA가 끼어 있으면 인증서를 이미지에 주입해야 하는데, 그때는 보안 가이드를 반드시 따르세요.
8. 검증 순서와 자주 보는 오류
호스트 터미널에서 먼저 curl -x http://127.0.0.1:7890 -I https://example.com처럼 Clash 포트가 응답하는지 확인합니다. 다음으로 컨테이너 안에서 curl -x http://host.docker.internal:7890 -I …를 시험합니다. 호스트는 되는데 컨테이너만 안 되면 DNS 이름 해석 실패인지, TCP 거절인지 나눕니다. Connection refused는 대개 allow-lan이 꺼져 있거나 방화벽이 막은 경우이고, 이름은 풀리는데 응답이 없으면 노드·규칙 쪽을 Clash에서 점검합니다.
docker pull만 실패하고 앱 컨테이너는 정상이라면 §4의 Desktop 수동 프록시를 우선 의심합니다. 반대로 풀은 되는데 docker compose run 속 패키지 매니저만 실패하면 §5·§6의 환경 변수 누락일 가능성이 큽니다. 그 밖의 Clash 자체 오류는 일반 트러블슈팅 가이드의 포트·구독 절차와 연계해 보세요.
9. 보안·운영 짧은 메모
프록시 URL에 토큰이나 사용자 이름이 들어가면 이미지 레이어·Compose 파일·CI 로그에 남을 수 있습니다. 가능하면 익명 프록시 포트만 쓰고, 민감 정보는 시크릿 관리 도구로 주입하세요. allow-lan을 켠 채로 공용 Wi-Fi에 장시간 연결하는 것은 같은 서브넷의 다른 단말에서 포트 스캔에 노출될 수 있으니 주의합니다.
10. 정리
macOS에서 Docker 컨테이너가 호스트 Clash를 쓰게 만드는 핵심은 127.0.0.1 착각을 버리고 host.docker.internal과 HTTP_PROXY 계열을 맞추는 것입니다. Clash의 mixed-port와 allow-lan을 연 뒤 Docker Desktop 수동 프록시로 엔진 출구를 고정하고, 실행·Compose·빌드 각각에 필요한 환경 변수를 한 번에 적어 두면 docker pull·apt·npm을 같은 축으로 묶을 수 있습니다. Windows·WSL2 조합은 별도 가상 네트워크라 WSL2 가이드의 호스트 IP 패턴을 따르는 편이 맞고, 본문은 순수 macOS Docker Desktop 독자를 위한 경로입니다.
GUI로 연결 로그와 프로필 전환을 같이 다루고 싶다면 Mihomo 기반 클라이언트를 기준으로 잡는 것이 재현성과 교차 검증에 유리합니다. 비슷한 도구들과 비교해도 Clash 계열은 규칙·로그·업데이트가 한 화면에 모여 있어 원인 분리가 수월한 편입니다. → Clash를 무료로 다운로드하고 Docker 컨테이너와 macOS 호스트를 같은 프록시 축으로 맞춰 보세요