기업 네트워크에서 Docker를 사용하거나 접근이 제한된 서버에서 작업하거나, 특정 IP를 통해 컨테이너가 인터넷에 접속하도록 하려면 어떻게 해야 할까요? 올바른 프록시 설정이 없으면 Docker는 Docker Hub 또는 다른 레지스트리에서 이미지를 다운로드할 수 없습니다. 이 기사에서는 데몬에서 개별 컨테이너까지 모든 프록시 수준을 다룰 것입니다.
Docker에 프록시가 필요한 이유
Docker는 외부 리소스에 지속적으로 접근하는 도구입니다. 매번 docker pull 또는 docker build를 실행할 때, 데몬은 Docker Hub, GitHub Container Registry, Google Container Registry 또는 개인 레지스트리에 접근합니다. 이때 문제가 발생합니다.
프록시 없이는 해결할 수 없는 상황은 다음과 같습니다:
- 방화벽이 있는 기업 네트워크 — 모든 트래픽은 기업 프록시 서버를 통해 이동해야 하며, 그렇지 않으면 연결이 차단됩니다.
- 지리적 제한 — Docker Hub 또는 특정 레지스트리가 귀하의 국가나 데이터 센터에서 접근할 수 없습니다.
- 인터넷에 직접 연결되지 않은 제한된 서버 — 인터넷에 접근할 수 있는 게이트웨이를 통해서만 접근 가능한 폐쇄형 VPS.
- 컨테이너의 아웃바운드 트래픽 제어 — 컨테이너 내 애플리케이션이 특정 IP를 통해 인터넷에 접근하도록 하고 싶습니다. 예를 들어, 파싱, API 요청 또는 지리적 콘텐츠 테스트를 위해서입니다.
- 요청 익명화 — 컨테이너에서 외부 서비스에 접근할 때 서버의 실제 IP를 숨깁니다.
- 요율 제한 — Docker Hub는 익명 사용자에 대해 풀 요청 수를 제한합니다 (6시간에 100번의 풀). IP를 회전시키는 프록시를 통해 이 제한을 우회할 수 있습니다.
Docker는 단일 애플리케이션이 아니라 여러 구성 요소로 이루어진 시스템이라는 점을 이해하는 것이 중요합니다. 데몬 (dockerd)은 호스트에서 실행되며 이미지를 풀합니다. 컨테이너는 고유한 네트워크 설정을 가진 격리된 프로세스입니다. 따라서 데몬과 컨테이너에 대한 프록시 설정은 서로 다른 두 가지 작업이며, 각각 다르게 해결해야 합니다.
Docker의 세 가지 프록시 수준
구성 파일을 수정하기 전에 아키텍처를 이해해야 합니다. Docker에는 각각 별도의 프록시 설정이 필요한 세 가지 독립적인 수준이 있습니다:
| 수준 | 작업 내용 | 어디에서 설정하는가 |
|---|---|---|
| Docker 데몬 | 이미지를 다운로드 (docker pull), 레지스트리에 접근 | systemd override 또는 daemon.json |
| Docker 빌드 | 이미지 빌드 시 명령 실행 (RUN apt-get, pip install 등) | Dockerfile의 ARG 및 ENV 또는 --build-arg |
| 런타임 컨테이너 | HTTP 요청을 수행하는 실행 중인 애플리케이션 | docker run 시 ENV 또는 docker-compose.yml에서 |
일반적인 실수는 하나의 수준에서만 프록시를 설정하고, 왜 이미지가 여전히 다운로드되지 않거나 애플리케이션이 프록시를 인식하지 못하는지 놀라는 것입니다. 각 수준을 자세히 살펴보겠습니다.
Docker 데몬을 위한 프록시 설정 (이미지 풀)
프록시를 통해 이미지를 다운로드하려면 가장 중요한 설정입니다. Docker 데몬은 systemd를 통해 실행되는 시스템 서비스입니다. 사용자 또는 root의 환경 변수를 인식하지 못합니다. 서비스의 환경에 프록시를 전달해야 합니다.
방법 1: systemd override를 통한 설정 (권장)
서비스 설정을 재정의할 디렉토리를 만듭니다:
sudo mkdir -p /etc/systemd/system/docker.service.d
다음 내용을 가진 파일 /etc/systemd/system/docker.service.d/http-proxy.conf를 만듭니다:
[Service] Environment="HTTP_PROXY=http://username:password@proxy-host:port" Environment="HTTPS_PROXY=http://username:password@proxy-host:port" Environment="NO_PROXY=localhost,127.0.0.1,::1,your-private-registry.example.com"
인증이 필요 없는 프록시인 경우 username:password@를 제거하면 됩니다. 파일을 만든 후 systemd 구성을 재로드하고 Docker를 재시작합니다:
sudo systemctl daemon-reload sudo systemctl restart docker
설정이 적용되었는지 확인합니다:
sudo systemctl show --property=Environment docker
출력에 HTTP_PROXY 및 HTTPS_PROXY 변수가 나타나야 합니다.
방법 2: ~/.docker/config.json을 통한 설정 (Docker Desktop 및 최신 버전)
Docker Engine 23.0부터는 클라이언트 구성 파일을 통해 프록시를 설정하는 더 편리한 방법이 생겼습니다. 파일 ~/.docker/config.json를 생성하거나 수정합니다:
{
"proxies": {
"default": {
"httpProxy": "http://username:password@proxy-host:port",
"httpsProxy": "http://username:password@proxy-host:port",
"noProxy": "localhost,127.0.0.1,::1"
}
}
}
이 방법은 root 권한이 필요 없고 데몬을 재시작할 필요가 없다는 장점이 있습니다. 설정은 빌드 시 자동으로 컨테이너에 전달됩니다. 그러나 데몬의 풀 요청을 관리하려면 여전히 systemd를 통한 방법이 필요합니다.
💡 NO_PROXY에 대한 중요 사항
항상 NO_PROXY에 내부 레지스트리 및 서비스의 주소를 추가하세요. 그렇지 않으면 Docker가 프록시를 통해 접근하려고 시도하고 연결 오류가 발생합니다. 일반적인 목록: localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
빌드 시 컨테이너 내 프록시
Docker가 이미지를 빌드하고 RUN apt-get install, RUN pip install 또는 RUN npm install과 같은 명령을 실행할 때, 이러한 명령은 임시 컨테이너 내에서 실행됩니다. 기본적으로 호스트의 프록시에 접근할 수 없습니다. 빌드 인수로 프록시를 명시적으로 전달해야 합니다.
--build-arg를 통한 프록시 전달
docker build \ --build-arg HTTP_PROXY=http://proxy-host:port \ --build-arg HTTPS_PROXY=http://proxy-host:port \ --build-arg NO_PROXY=localhost,127.0.0.1 \ -t my-image .
Dockerfile에서의 설정
빌드 단계에서만 프록시가 필요하고 최종 이미지에 포함되기를 원하지 않는 경우 ARG를 사용하고 ENV는 사용하지 마세요:
FROM ubuntu:22.04 # 빌드 인수 선언 ARG HTTP_PROXY ARG HTTPS_PROXY ARG NO_PROXY # 명령에서 사용 RUN apt-get update && apt-get install -y curl wget # 이 블록 이후에는 컨테이너의 ENV에 프록시가 포함되지 않습니다.
만약 실행 중인 컨테이너에서도 프록시가 필요하다면 ENV를 사용하세요:
FROM python:3.11 ENV HTTP_PROXY=http://proxy-host:port ENV HTTPS_PROXY=http://proxy-host:port ENV NO_PROXY=localhost,127.0.0.1 RUN pip install requests beautifulsoup4 CMD ["python", "app.py"]
⚠️ 주의: 보안
이미지가 공개되거나 리포지토리에 포함될 경우 Dockerfile에 프록시 로그인 및 비밀번호를 하드코딩하지 마세요. --build-arg를 사용하고 CI/CD 시스템의 환경 변수에서 값을 전달하세요. docker history 명령은 ARG 값을 보여줄 수 있으므로 비밀 정보에는 Docker BuildKit 비밀을 사용하세요.
실행 중인 컨테이너를 위한 프록시
이는 HTTP 요청을 수행하는 애플리케이션에 가장 일반적인 시나리오입니다 — 파서, 봇, 특정 IP를 통해 접근해야 하는 마이크로서비스 등. 이 경우 설정은 매우 간단합니다: 컨테이너 실행 시 환경 변수를 전달하면 됩니다.
docker run을 통한 설정
docker run \ -e HTTP_PROXY=http://username:password@proxy-host:port \ -e HTTPS_PROXY=http://username:password@proxy-host:port \ -e NO_PROXY=localhost,127.0.0.1 \ my-image
대부분의 인기 HTTP 라이브러리는 자동으로 HTTP_PROXY 및 HTTPS_PROXY 변수를 인식합니다: Python requests, curl, wget, Go의 net/http, Node.js https-proxy-agent 등. 코드에 추가로 작성할 필요가 없습니다.
.env 파일을 통한 설정
설정을 .env 파일에 저장하고 전체를 전달하는 것이 더 편리합니다:
# .env 파일 HTTP_PROXY=http://username:password@proxy-host:port HTTPS_PROXY=http://username:password@proxy-host:port NO_PROXY=localhost,127.0.0.1
docker run --env-file .env my-image
컨테이너 내 SOCKS5 프록시
SOCKS5 프록시를 사용하는 경우 (예: 주거용 프록시는 주로 이 프로토콜을 지원합니다), 구문이 약간 다릅니다:
docker run \ -e ALL_PROXY=socks5://username:password@proxy-host:port \ my-image
주의: 모든 라이브러리가 추가 종속성 없이 환경 변수를 통해 SOCKS5를 지원하지는 않습니다. Python requests는 requests[socks]를 설치해야 하며, curl은 libcurl-socks 지원으로 빌드되어야 합니다.
Docker Compose에서의 프록시
실제 프로젝트에서는 일반적으로 docker run을 사용하지 않습니다. 대부분 Docker Compose와 함께 작업하며, 여러 서비스가 하나의 파일에 정의됩니다. 여기서 프록시 설정은 각 서비스 수준에서 또는 변수 파일을 통해 수행됩니다.
옵션 1: docker-compose.yml의 environment
version: '3.8'
services:
scraper:
image: my-scraper:latest
environment:
- HTTP_PROXY=http://username:password@proxy-host:port
- HTTPS_PROXY=http://username:password@proxy-host:port
- NO_PROXY=localhost,127.0.0.1
restart: unless-stopped
api:
image: my-api:latest
# 이 서비스는 프록시 없이 내부 리소스에만 접근합니다.
ports:
- "8080:8080"
옵션 2: .env 파일을 통한 설정 (권장)
docker-compose.yml이 있는 디렉토리에 .env 파일을 생성하세요. Docker Compose는 이 파일을 자동으로 인식합니다:
# .env PROXY_HOST=proxy-host PROXY_PORT=8080 PROXY_USER=username PROXY_PASS=password
version: '3.8'
services:
scraper:
image: my-scraper:latest
environment:
- HTTP_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- HTTPS_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- NO_PROXY=localhost,127.0.0.1
Compose에서 빌드 시 프록시 설정
Compose에 build 섹션이 있고 빌드 단계에서 프록시를 전달해야 하는 경우:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
HTTP_PROXY: http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
HTTPS_PROXY: http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
environment:
- HTTP_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- HTTPS_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
Docker에 적합한 프록시 유형 선택하기
프록시 유형 선택은 작업에 따라 다릅니다. Docker 환경에 적합한 세 가지 주요 유형이 있으며, 각각의 특정 용도가 있습니다:
| 프록시 유형 | 속도 | 익명성 | Docker에 적합한 시나리오 |
|---|---|---|---|
| 데이터 센터 | ⚡ 높음 | 중간 | 이미지 풀, CI/CD 파이프라인, Docker Hub의 요율 제한 우회 |
| 주거용 | 🔄 중간 | 높음 | 보호된 사이트 파싱, 지리적 제한이 있는 API, 테스트 |
| 모바일 | 🔄 중간 | 최대 | 모바일 API와 함께 작동하는 애플리케이션 (Instagram, TikTok) |
이미지 풀 및 CI/CD에 가장 적합한 것은 데이터 센터 프록시입니다 — 대용량 이미지(수 기가바이트)를 다운로드할 때 최대 속도와 안정적인 연결을 제공합니다.
사이트 파서를 위한 컨테이너는 보호된 사이트를 우회하고 일반 사용자처럼 보이기 위해 주거용 프록시가 더 적합합니다. 이들은 실제 가정 사용자의 IP를 가지고 있어 집중적인 요청에도 차단될 가능성이 낮습니다.
모바일 플랫폼과 함께 작동하는 애플리케이션 — Instagram Graph API, TikTok API, 서비스의 모바일 버전 — 에 대해서는 모바일 프록시를 고려해야 합니다. 이들은 이동통신사의 IP를 사용하여 안티봇 시스템에서 최소한의 의심을 받습니다.
프로토콜: HTTP vs SOCKS5
Docker 데몬은 HTTP/HTTPS 프록시만 지원하며, 이미지를 풀하기 위해 SOCKS5는 작동하지 않습니다. SOCKS5 프록시가 필요하고 이미지를 다운로드해야 하는 경우, privoxy 또는 microsocks와 같은 로컬 변환기를 사용해야 합니다. 이는 HTTP를 수신하고 SOCKS5를 통해 프록시합니다.
런타임 컨테이너의 경우 상황이 더 좋습니다: 대부분의 HTTP 라이브러리는 ALL_PROXY=socks5://...를 통해 SOCKS5를 직접 지원합니다.
자주 발생하는 오류 및 해결 방법
Docker에서 프록시 설정 시 가장 흔히 발생하는 문제를 살펴보겠습니다:
오류 1: 프록시가 호스트에 설정되어 있지만 Docker가 인식하지 못함
증상:
Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp: connection refused
원인: Docker 데몬은 시스템 서비스로 실행되며 현재 사용자의 환경 변수를 상속받지 않습니다. 터미널에서 export HTTPS_PROXY=...를 설정했더라도 인식하지 못합니다.
해결 방법: systemd override를 통해 프록시를 설정하세요 (위 섹션의 방법 1). 반드시 systemctl daemon-reload && systemctl restart docker를 실행하세요.
오류 2: apt-get / pip / npm이 빌드 시 작동하지 않음
증상:
Err:1 http://archive.ubuntu.com/ubuntu focal InRelease
Could not connect to archive.ubuntu.com:80
원인: 데몬에 대한 프록시 설정은 Dockerfile의 RUN 명령 내에서는 자동으로 전파되지 않습니다. 이는 서로 다른 두 가지 컨텍스트입니다.
해결 방법: 프록시를 --build-arg를 통해 전달하거나 ~/.docker/config.json을 사용하여 proxies 섹션을 추가하세요 (Docker 23.0 이상, 빌드 시 자동으로 인수 전달).
오류 3: 내부 서비스가 프록시를 통해 접근할 수 없음
증상:
curl: (7) Failed to connect to internal-service.local port 8080: Connection refused
원인: 프록시가 외부에서 접근할 수 없는 내부 주소에 대한 요청을 프록시하려고 시도합니다.
해결 방법: 모든 내부 도메인 및 서브넷을 NO_PROXY에 추가하세요. 기업 네트워크의 경우 일반적인 목록: localhost,127.0.0.1,::1,.internal,.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
오류 4: 프록시 인증이 작동하지 않음 — 비밀번호에 특수 문자가 포함됨
비밀번호에 특수 문자가 포함된 경우 (@, #, %) URL이 올바르게 파싱되지 않습니다.
해결 방법: URL 인코딩으로 비밀번호를 인코딩하세요. 예를 들어, p@ss#word → p%40ss%23word. Python에서는 인코딩된 버전을 빠르게 얻을 수 있습니다:
python3 -c "from urllib.parse import quote; print(quote('p@ss#word', safe=''))"
# 출력: p%40ss%23word
오류 5: 프록시는 작동하지만 SSL 인증서가 확인되지 않음
기업 프록시는 종종 SSL 검사를 수행하고 (MITM) 인증서를 변경합니다. 이 경우 Docker는 다음과 같은 오류를 발생시킵니다:
x509: certificate signed by unknown authority
해결 방법: 기업의 루트 인증서를 신뢰할 수 있는 목록에 추가하고 Docker를 재시작하세요. Ubuntu/Debian에서는:
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates sudo systemctl restart docker
오류 6: Kubernetes / Docker Swarm — 모든 노드에서 프록시가 작동하지 않음
클러스터 환경에서는 각 노드에 대해 프록시를 별도로 설정해야 합니다. Kubernetes의 경우 추가로 파드의 매니페스트 또는 ConfigMap에서 환경 변수를 설정해야 합니다. 이는 Ansible 또는 기타 구성 관리 도구를 통해 자동화할 수 있습니다.
결론
Docker에서 프록시 설정은 단일 설정이 아니라 이미지 풀을 위한 데몬, 빌드 타임을 위한 설정, 실행 중인 컨테이너를 위한 설정의 세 가지 독립적인 수준입니다. 이 아키텍처를 이해하면 어떤 트래픽이 프록스를 통해 이동하고 어떤 트래픽이 직접 이동하는지를 유연하게 관리할 수 있습니다.
올바른 설정을 위한 간단한 체크리스트:
- ✅ 이미지 풀을 위한 — HTTP_PROXY 및 HTTPS_PROXY 변수를 설정한 systemd override를 설정하세요
- ✅ 빌드를 위한 —
--build-arg또는~/.docker/config.json을 통해 프록시를 전달하세요 - ✅ 런타임을 위한 —
-e또는--env-file을 통해 환경 변수를 사용하세요 - ✅ 항상 내부 주소에 대한 NO_PROXY를 설정하세요
- ✅ Dockerfile에 프록시 비밀번호를 저장하지 마세요 — build-args 및 .env 파일을 사용하세요
- ✅ CI/CD 파이프라인을 위해 빠른 데이터 센터 프록시를 선택하세요
- ✅ 파서 컨테이너를 위해 — 주거용 또는 모바일 프록시를 사용하세요
컨테이너 애플리케이션이 외부 서비스에 요청을 하거나 데이터를 파싱하거나 지리적 제한이 있는 API와 작업하는 경우, 주거용 프록시를 사용하는 것이 좋습니다 — 이들은 서비스 측에서 높은 신뢰도를 제공하며 집중적인 작업에도 차단 위험이 최소화됩니다.