개발 및 AI · Clash 기술 블로그

VS Code Dev Containers에서 Clash 프록시를 사용하는 방법

Dev Container 빌드, 컨테이너 내부 Git 및 확장 프로그램 다운로드가 반드시 같은 프록시를 사용하는 것은 아닙니다. 각 실패가 어느 계층에서 발생하는지 먼저 찾고 호스트 주소와 NO_PROXY를 해당 계층으로 전달하세요.

  • VS Code
  • Dev Containers
  • Docker
  • 프록시
이 글의 목차

시간 초과가 Docker, 빌드 또는 실행 중인 컨테이너 중 어디에서 발생하는지 먼저 확인

VS Code Dev Containers에서 Clash를 사용하려면 프록시 주소 하나만 입력해서는 안 됩니다. 기본 이미지 가져오기, Dockerfile 실행, 컨테이너 내부 Git 실행과 원격 확장 기능 다운로드는 서로 다른 프로세스에서 시작됩니다.

'Dev Containers' 출력에서 실패 명령이 속한 단계를 찾고 해당 프로세스에 프록시를 전달하세요. 그렇지 않으면 containerEnv를 아무리 완전하게 작성해도 아직 생성되지 않은 컨테이너 문제를 고칠 수 없습니다.

네 가지 네트워크 위치

실패한 작업실제로 요청하는 구성 요소구성 진입점
docker pull / 기본 이미지 가져오기Docker daemonDocker Desktop 또는 daemon 프록시
Dockerfile RUN apt/npm이미지 빌드 단계build args 또는 BuildKit 구성
컨테이너 내부 curl/Git/pip현재 실행 중인 컨테이너containerEnv, remoteEnv 또는 도구 구성
VS Code 원격 확장 기능 다운로드Dev Containers 원격 서비스컨테이너 환경과 확장 기능 자체의 네트워크 요청

오류가 'Starting Dev Container' 전에 나타나면 컨테이너가 아직 실행되지 않았으므로 대개 .devcontainer/devcontainer.json의 containerEnv로 고칠 수 없습니다. 실패 명령과 VS Code 출력 패널의 단계를 먼저 기록하세요.

Dev Containers의 네 가지 네트워크 구간
  1. VS Code 호스트확장 기능을 가져오고 Docker를 시작
  2. Docker 빌드 단계build args 또는 Docker 프록시 설정 읽기
  3. 실행 중인 컨테이너containerEnv 읽기
  4. 원격 확장 기능과 터미널remoteEnv 또는 도구 자체 구성 읽기

실패한 구간에만 프록시를 설정하세요. 호스트, 빌드 단계와 컨테이너 런타임을 같은 환경 변수 묶음으로 간주하지 마세요.

컨테이너의 127.0.0.1은 호스트가 아닙니다

Clash가 호스트에서 실행될 때 컨테이너의 http://127.0.0.1:7890은 컨테이너 자체를 가리킵니다. Windows와 macOS의 Docker Desktop은 host.docker.internal을 제공하며 Linux Docker에서는 host-gateway를 통해 같은 이름을 매핑할 수 있습니다.

Clash가 호스트 루프백 주소에서만 수신한다면 컨테이너에서 여전히 연결할 수 없을 수 있습니다. 로컬 네트워크 허용을 켜거나 mixed-port가 컨테이너에서 접근 가능한 인터페이스를 수신하게 할 때는 시스템 방화벽으로 출처도 제한하세요. 7890을 공용 네트워크에 직접 노출하면 안 됩니다.

컨테이너에서 포트부터 검증, CLASH_PORT는 클라이언트의 실제 mixed-port에 맞게 수정
CLASH_HOST=host.docker.internal
CLASH_PORT=7890
getent hosts "$CLASH_HOST" || true
curl -v -x "http://$CLASH_HOST:$CLASH_PORT" https://www.example.com/

# Linux Docker 临时测试
docker run --rm --add-host=host.docker.internal:host-gateway curlimages/curl:latest -v -x http://host.docker.internal:7890 https://www.example.com/

Linux 호스트에서는 host-gateway를 devcontainer.json에 기록

.devcontainer/devcontainer.json 일부
{
  "runArgs": [
    "--add-host=host.docker.internal:host-gateway"
  ]
}

수정 후 Dev Containers: Rebuild Container를 실행하세요. 기존 컨테이너에는 새 hosts 매핑이 자동으로 적용되지 않습니다.

Docker Compose 프로젝트에서는 서비스 아래에 extra_hosts: ["host.docker.internal:host-gateway"]를 사용합니다. 두 곳에 중복해서 추가하지 마세요.

포트에 접근할 수 있게 된 뒤 실행 중인 컨테이너에 프록시 설정

이전 단계는 curl에서 HTTP 응답을 받은 뒤에야 통과한 것입니다. 다음으로 같은 주소를 컨테이너 내부 터미널과 VS Code 원격 프로세스에 전달하세요. mixed-port가 7890이 아니라면 아래 세 곳의 포트를 함께 수정해야 합니다.

프로젝트 수준 devcontainer.json 예시
{
  "containerEnv": {
    "HTTP_PROXY": "http://host.docker.internal:7890",
    "HTTPS_PROXY": "http://host.docker.internal:7890",
    "NO_PROXY": "localhost,127.0.0.1,::1,host.docker.internal"
  },
  "remoteEnv": {
    "HTTP_PROXY": "${containerEnv:HTTP_PROXY}",
    "HTTPS_PROXY": "${containerEnv:HTTPS_PROXY}",
    "NO_PROXY": "${containerEnv:NO_PROXY}"
  }
}

HTTPS_PROXY를 http://로 시작하는 것이 일반적으로 맞습니다. HTTP CONNECT 프록시를 통해 HTTPS에 접근한다는 뜻이며 프록시 포트 자체가 HTTPS로 바뀌는 것은 아닙니다. NO_PROXY에는 Compose 서비스 이름, 회사 내부망과 로컬 개발 도메인도 추가해야 합니다. 그렇지 않으면 컨테이너가 데이터베이스 또는 API에 접근할 때 Clash를 우회 경로가 아니라 불필요한 경로로 사용할 수 있습니다.

저장 후 Rebuild Container를 실행하고 새 터미널에서 env | grep -i proxy를 실행합니다. 기존 터미널 프로세스에는 remoteEnv가 자동으로 갱신되지 않습니다.

이미지 가져오기와 Dockerfile 빌드는 별도로 구성

FROM 이미지 가져오기는 Docker daemon에서 시작하므로 Docker Desktop의 Proxies 페이지 또는 daemon 구성에서 처리해야 합니다. Dockerfile의 RUN 명령은 빌드 컨테이너에서 실행되므로 build.args로 임시 프록시를 전달할 수 있습니다. 개인 포트나 인증 정보를 ENV로 기록해 이미지 계층에 남기지 마세요.

devcontainer 빌드 매개변수 일부, 7890은 실제 mixed-port에 맞게 수정
{
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "HTTP_PROXY": "http://host.docker.internal:7890",
      "HTTPS_PROXY": "http://host.docker.internal:7890",
      "NO_PROXY": "localhost,127.0.0.1"
    }
  }
}

curl 성공 후에도 Git, apt와 확장 기능을 각각 테스트

컨테이너 터미널에서 HTTP_PROXY, HTTPS_PROXY와 NO_PROXY가 나타나는지 확인한 뒤 Git과 패키지 관리자 명령을 각각 실행합니다. Git은 대개 환경 변수를 읽지만 계속 기존 주소를 가리킨다면 Git 자체 구성도 확인하세요.

원격 확장 기능은 컨테이너 측 VS Code Server에서 다운로드합니다. remoteEnv를 수정한 뒤 Rebuild Container를 실행하고 출력 패널에서 marketplace 요청을 관찰하세요.

컨테이너 내부 Git 확인, REPO_URL은 프로젝트의 공개 저장소로 교체 가능
REPO_URL='https://github.com/octocat/Hello-World.git'
env | grep -i '_proxy'
git config --show-origin --get-regexp 'http.*proxy' || true
git ls-remote "$REPO_URL"

# 只有 Git 不读取环境变量时才临时写入
git config --global http.proxy http://host.docker.internal:7890
git config --global --unset-all http.proxy

curl은 성공하지만 Git은 계속 기존 포트에 연결

git config --show-origin --get-regexp 'http.*proxy'를 실행하고 사용자 또는 저장소에 남은 설정을 정리합니다.

apt update 실패

실제 저장소 도메인, 인증서와 /etc/apt/apt.conf.d의 별도 프록시를 확인합니다.

프로젝트는 실행되지만 원격 확장 기능을 설치할 수 없음

Dev Containers 로그에서 marketplace 요청을 찾고 원격 서비스가 remoteEnv를 상속했는지 확인합니다.

컨테이너는 인터넷에 연결되지만 로컬 데이터베이스 연결 실패

데이터베이스 서비스 이름과 사설 대역을 NO_PROXY에 추가합니다.

Rebuild 단계에서 계속 실패

daemon의 이미지 가져오기 또는 Dockerfile build args로 돌아가고 실행 중인 컨테이너는 더 수정하지 않습니다.

공유 저장소에 개인 프록시를 고정하지 마세요

팀 프로젝트에서는 devcontainer.json의 ${localEnv:HTTP_PROXY}로 개발자 로컬 환경을 읽거나 실제 주소가 없는 .env.example을 제공할 수 있습니다.

README에 HTTP_PROXY가 http://host.docker.internal:7890 같은 전체 주소여야 한다고 명시하세요. 커밋 전에 프록시 URL에 사용자 이름, 비밀번호와 token이 없는지 확인합니다.

검증을 마치면 docker pull, Rebuild Container, 컨테이너 내부 Git/패키지 관리자와 로컬 서비스 접근을 각각 완료할 수 있어야 합니다. 실패한 항목의 단계별 로그를 보존하고 네 계층의 네트워크를 다시 'Dev Container에서 인터넷이 안 됨' 하나로 섞지 마세요.

참고 자료