테마 전환

Docker 컨테이너에서 호스트에 접근하는 방법: host.docker.internal 완벽 가이드

Easton editorial illustration: observability control panel

금요일 오후 3시, 터미널에 뜬 Connection refused 오류를 바라보고 있었습니다.

솔직히 그때는 꽤 막막했습니다. 로컬 MySQL은 멀쩡하게 실행 중이었고 Navicat에서도, 명령줄에서도 접속할 수 있었는데 컨테이너 안의 애플리케이션에서만 연결되지 않았습니다. 연결 문자열 localhost:3306도 세 번이나 확인했지만 틀린 곳이 없었습니다. 사용자 이름과 비밀번호도 맞았습니다. 대체 무엇이 문제였을까요?

결국 문제는 localhost라는 단어 자체에 있었습니다.

Docker 컨테이너 안에서 localhost127.0.0.1로 호스트 서비스에 연결하려 할 때마다 실패한 적이 있다면 이 글이 도움이 될 것입니다. 컨테이너 안의 localhost가 우리가 생각하는 localhost와 왜 다른지, 그리고 host.docker.internal이라는 ‘마법의 도메인’으로 이 문제를 깔끔하게 해결하는 방법을 최대한 쉽게 설명하겠습니다.

이 글에서 다루는 내용은 다음과 같습니다.

  • 어려운 전문 용어 없이 이해하는 컨테이너 네트워크 격리 원리
  • Mac, Windows, Linux에서 올바르게 설정하는 방법
  • 다음에 바로 꺼내 볼 수 있는 실용적인 문제 해결 체크리스트

localhost가 작동하지 않는 이유

먼저 답부터 말하면 컨테이너에는 독립된 네트워크 공간이 있습니다.

조금 추상적으로 들리나요? 컨테이너를 독립된 작은 집이라고 생각해 봅시다. 그 집에는 고유한 주소와 우편함, 그리고 독립된 모든 것이 있습니다. 컨테이너 안에서 ‘localhost’를 부르거나 127.0.0.1 주소를 입력하면 실제로 찾는 대상은 그 작은 집 자체이지, 집 밖에 있는 호스트가 아닙니다.

구체적으로는 다음과 같습니다.

  • 호스트에서 localhost는 호스트 자신을 가리킵니다.
  • 컨테이너에서 localhost는 컨테이너 자신을 가리킵니다.
  • localhost는 전혀 다른 대상입니다.

저도 처음 알았을 때는 꽤 놀랐습니다. MySQL이 컴퓨터에서 잘 실행되고 있는데 왜 컨테이너에서는 연결되지 않을까요? 바로 이 때문입니다. 컨테이너가 자기 공간 안에서 MySQL을 찾고 있으니 당연히 찾을 수 없습니다.

컨테이너의 네트워크 격리 구조

Docker는 각 컨테이너에 독립된 ‘네트워크 네임스페이스’를 만듭니다. 이 용어를 어렵게 생각할 필요는 없습니다. 다음처럼 이해하면 됩니다.

컨테이너마다 고유한 네트워크 인터페이스, IP 주소, 라우팅 테이블이 있습니다. 같은 건물에 사는 우리 집과 이웃집이 서로 다른 Wi-Fi 비밀번호를 사용하며 간섭하지 않는 것과 비슷합니다.

컨테이너와 호스트는 docker0이라는 가상 브리지로 연결됩니다. 컨테이너 IP는 일반적으로 172.17.0.x 형식이고, 컨테이너에서 보이는 호스트 IP는 172.17.0.1, 즉 브리지의 게이트웨이 주소입니다.

컨테이너 안에서 localhost에 접근하면 호스트의 127.0.0.1이 아니라 컨테이너의 127.0.0.1에 접근합니다. 따라서 호스트의 MySQL에 연결할 수 없습니다.

실제 오류 메시지를 살펴보면 다음과 같습니다.

Error: connect ECONNREFUSED 127.0.0.1:3306

또는 다음과 같이 표시됩니다.

Can't connect to MySQL server on 'localhost' (111)

이것이 바로 컨테이너 안에서 localhost로 호스트 서비스에 연결할 때 나타나는 대표적인 오류입니다.

host.docker.internal이란?

localhost가 작동하지 않는다면 컨테이너에서 호스트에 어떻게 접근해야 할까요?

Docker는 host.docker.internal이라는 깔끔한 해결 방법을 제공합니다. 호스트의 IP 주소로 자동 해석되는 특수 도메인입니다. 실제 IP가 무엇이든 이 이름으로 호스트를 찾을 수 있으므로 호스트의 ‘별칭’이라고 생각하면 됩니다.

예를 들어 호스트의 MySQL이 3306 포트를 수신하고 있다면 컨테이너 안에서 다음과 같이 연결하면 됩니다.

mysql://user:[email protected]:3306/dbname

호스트 IP가 192.168.1.100인지 10.0.0.5인지 신경 쓸 필요가 없습니다. 네트워크 환경이 바뀌어 IP가 달라져도 걱정하지 않아도 됩니다. host.docker.internal이 올바른 주소를 자동으로 가리킵니다.

편리하지 않나요?

버전 및 플랫폼 지원

다만 여기에는 주의할 점이 하나 있습니다.

Mac 및 Windows 사용자(Docker Desktop)

그래픽 인터페이스가 있는 Docker Desktop을 사용한다면 18.03 버전(2018년 3월)부터 host.docker.internal을 기본 지원합니다. 별도 설정 없이 바로 사용할 수 있습니다.

코드에 host.docker.internal을 그대로 쓰면 됩니다.

const mysql = require('mysql2');
const connection = mysql.createConnection({
  host: 'host.docker.internal',  // 이것만 바꾸면 됩니다
  port: 3306,
  user: 'root',
  password: 'your_password'
});

Linux 사용자(Docker Engine)

Linux에서는 상황이 조금 다릅니다. Docker가 Mac이나 Windows처럼 가상 머신을 거치지 않고 시스템에서 직접 실행되기 때문에 host.docker.internal이 기본으로 존재하지 않습니다.

다행히 Docker Engine 20.10 버전(2020년 12월)부터는 설정을 추가해 직접 활성화할 수 있습니다. 구체적인 방법은 다음 절에서 자세히 설명합니다.

Docker 버전이 더 오래됐다면 다음과 같은 대안도 있습니다.

  • Docker 기본 브리지의 게이트웨이 IP인 172.17.0.1 사용
  • Docker 네트워크에서 호스트가 사용하는 실제 IP 사용
  • docker.for.mac.host.internal 사용(구형 Mac 버전만 해당)

세 플랫폼의 설정 방법

이제 바로 복사해 사용할 수 있는 설정을 살펴보겠습니다.

Mac/Windows 설정(Docker Desktop)

가장 간단한 경우입니다.

방법 1: 코드에서 바로 사용

별도 설정 없이 코드에 host.docker.internal을 바로 쓰면 됩니다.

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    environment:
      - DB_HOST=host.docker.internal  # 바로 사용
      - DB_PORT=3306

방법 2: 명시적으로 선언(선택 사항)

필수는 아니지만 설정을 분명히 드러내고 싶다면 extra_hosts를 추가할 수도 있습니다.

version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - DB_HOST=host.docker.internal

host-gateway는 Docker 20.10 이상에서 사용할 수 있는 새 구문으로 ‘호스트 게이트웨이 주소’를 뜻합니다.

docker run 명령으로 실행한다면 다음과 같이 설정합니다.

docker run -d \
  --add-host=host.docker.internal:host-gateway \
  -e DB_HOST=host.docker.internal \
  myapp:latest

Linux 설정(Docker Engine)

Linux에서는 직접 설정해야 하므로 조금 더 번거롭습니다.

방법 1: 권장 방법 - host-gateway 사용

Docker 20.10 이상을 사용하는 모든 플랫폼에 적용할 수 있는 가장 일반적인 방법입니다.

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # 핵심 설정
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306

docker run에서는 다음과 같이 설정합니다.

docker run -d \
  --add-host=host.docker.internal:host-gateway \
  -e DB_HOST=host.docker.internal \
  myapp:latest

이 방법의 장점은 플랫폼에 관계없이 사용할 수 있다는 것입니다. 같은 설정을 Mac, Windows, Linux에서 수정 없이 사용할 수 있습니다.

방법 2: 대안 - Docker 브리지 IP 사용

Docker 버전이 너무 오래되어 host-gateway를 사용할 수 없다면 Docker 기본 브리지의 게이트웨이 주소를 사용할 수 있습니다.

version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:172.17.0.1"  # Docker 기본 게이트웨이
    environment:
      - DB_HOST=host.docker.internal

172.17.0.1은 Docker bridge 네트워크의 기본 게이트웨이입니다. Docker의 기본 네트워크 설정을 바꾸지 않았다면 대부분 이 IP가 맞습니다.

방법 3: 최후의 방법 - host 네트워크 모드

앞의 방법으로도 해결되지 않는다면 다음 방법이 있습니다.

docker run -d \
  --network=host \
  -e DB_HOST=localhost \  # 이때는 localhost를 사용할 수 있습니다
  myapp:latest

또는 Docker Compose에서 다음과 같이 설정합니다.

version: '3'
services:
  app:
    image: myapp:latest
    network_mode: "host"  # 호스트 네트워크 사용
    environment:
      - DB_HOST=localhost  # localhost를 바로 사용할 수 있음

이 방법의 장점: 단순하고 확실합니다. 컨테이너가 호스트의 네트워크 스택을 직접 사용하므로 localhost가 실제 호스트의 localhost를 뜻합니다.

이 방법의 단점:

  • 컨테이너의 네트워크 격리가 사라집니다.
  • 컨테이너와 호스트가 포트를 공유하므로 충돌할 수 있습니다. 예를 들어 컨테이너가 8080 포트를 사용하려는데 호스트가 이미 그 포트를 쓰고 있을 수 있습니다.
  • Linux에서만 사용할 수 있으며 Mac/Windows에서는 지원하지 않습니다.
  • 프로덕션 환경에서는 권장하지 않으며 로컬 개발과 디버깅에만 적합합니다.

여러 플랫폼에서 공통으로 사용하는 설정(강력 권장)

팀에 Mac 사용자와 Linux 사용자가 섞여 있거나 코드를 여러 환경에서 실행해야 한다면 다음 설정을 사용하세요.

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # 모든 플랫폼에서 인식
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=your_password

이 설정은 2020년 말에 출시된 Docker 20.10 이상의 모든 플랫폼에서 작동합니다. 아직도 2020년 이전 버전의 Docker를 사용하고 있다면 이제는 업그레이드할 때입니다.

호스트 서비스 설정 시 주의할 점

컨테이너 쪽 설정만 마쳤다고 끝난 것은 아닙니다.

호스트 서비스도 올바르게 설정해야 연결할 수 있습니다. 많은 사람이 놓치는 부분이라 따로 살펴보겠습니다.

서비스가 올바른 주소를 수신해야 합니다

가장 자주 발생하는 문제입니다.

많은 서비스는 기본적으로 127.0.0.1만 수신합니다. 즉, 로컬 컴퓨터에서 오는 연결만 허용합니다. 하지만 Docker 컨테이너는 호스트 입장에서 ‘로컬 컴퓨터’가 아니므로 Docker 브리지를 통해 들어오는 요청은 거부됩니다.

서비스가 0.0.0.0을 수신하게 해야 합니다. 이는 ‘모든 네트워크 인터페이스의 연결을 허용한다’는 뜻입니다.

MySQL 설정

MySQL 설정 파일은 일반적으로 다음 위치에 있습니다.

  • Linux: /etc/mysql/mysql.conf.d/mysqld.cnf
  • Mac(Homebrew): /usr/local/etc/my.cnf
  • Windows: C:\ProgramData\MySQL\MySQL Server 8.0\my.ini

bind-address를 수정합니다.

[mysqld]
# 기존 설정 예시
# bind-address = 127.0.0.1

# 다음과 같이 변경
bind-address = 0.0.0.0

수정한 뒤 MySQL을 재시작합니다.

# Linux
sudo systemctl restart mysql

# Mac
brew services restart mysql

# Windows
# 서비스 관리자에서 MySQL 서비스 재시작

Redis 설정

redis.conf를 편집합니다. 일반적으로 /etc/redis/redis.conf 또는 /usr/local/etc/redis.conf에 있습니다.

# 다음 줄 찾기
bind 127.0.0.1 -::1

# 다음과 같이 변경
bind 0.0.0.0

Redis를 재시작합니다.

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

PostgreSQL 설정

postgresql.conf를 편집합니다.

listen_addresses = '*'  # 모든 주소 수신

pg_hba.conf도 수정해 Docker 네트워크 대역의 접근을 허용해야 합니다.

# 이 줄을 추가해 172.17.0.0/16 네트워크 대역의 접근 허용
host    all             all             172.17.0.0/16           md5

사용자 권한 설정(MySQL)

MySQL이 0.0.0.0을 수신하도록 설정했더라도 사용자 권한을 확인해야 합니다.

MySQL 사용자 권한은 ‘사용자 이름@접속 호스트’ 단위로 관리됩니다. 예를 들어 root@localhostroot@%는 서로 다른 사용자입니다.

MySQL 사용자가 localhost에서 오는 접근만 허용한다면 컨테이너에서는 여전히 연결할 수 없습니다. Docker 네트워크 대역에서 접근할 수 있도록 사용자에게 권한을 부여해야 합니다.

-- 방법 1: 모든 호스트에서 접근 허용(간단하지만 안전성은 낮음)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%' IDENTIFIED BY 'your_password';

-- 방법 2: Docker 네트워크 대역에서만 접근 허용(더 안전함)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'172.17.0.%' IDENTIFIED BY 'your_password';

-- 권한 새로고침
FLUSH PRIVILEGES;

MySQL 8.0 이상에서는 구문이 약간 다릅니다.

-- 먼저 사용자 생성
CREATE USER 'your_user'@'%' IDENTIFIED BY 'your_password';

-- 그다음 권한 부여
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%';

FLUSH PRIVILEGES;

방화벽 설정

일부 시스템에서는 방화벽이 Docker 컨테이너에서 호스트 서비스로 들어오는 연결을 차단할 수 있습니다.

방화벽 상태 확인:

# Linux (ufw)
sudo ufw status

# Linux (firewalld)
sudo firewall-cmd --state

Docker 네트워크 대역 접근 허용(MySQL의 3306 포트 예시):

# ufw
sudo ufw allow from 172.17.0.0/16 to any port 3306

# firewalld
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="172.17.0.0/16" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload

보안 권장 사항

0.0.0.0을 수신하면 서비스가 네트워크의 다른 컴퓨터에 노출될 수 있으므로 보안 위험이 생깁니다.

프로덕션 환경에서의 방법:

  1. 특정 네트워크 인터페이스만 수신: Docker가 사용하는 인터페이스를 알고 있다면 해당 인터페이스만 수신합니다.

    bind-address = 172.17.0.1
  2. 방화벽 함께 사용: Docker 네트워크 대역만 허용하고 다른 출처의 접근은 차단합니다.

  3. 전용 데이터베이스 컨테이너 사용: 데이터베이스를 호스트에서 실행하지 말고 Docker Compose로 데이터베이스 컨테이너를 실행합니다. 애플리케이션 컨테이너와 데이터베이스 컨테이너를 같은 네트워크에 두는 편이 더 안전합니다.

로컬 개발 환경:

솔직히 로컬 개발에서는 0.0.0.0을 수신해도 크게 문제 되지 않습니다. 컴퓨터가 서버처럼 외부 인터넷에 직접 노출된 것도 아니므로 지나치게 걱정할 필요는 없습니다.

자주 발생하는 문제 해결 체크리스트

연결 문제가 생겼나요? 당황하지 말고 이 체크리스트를 순서대로 확인해 보세요.

문제 1: Connection refused(연결 거부)

가장 흔한 오류입니다. 오류 메시지는 다음과 같습니다.

Error: connect ECONNREFUSED host.docker.internal:3306

또는 다음과 같습니다.

Can't connect to MySQL server on 'host.docker.internal' (111)

가능한 원인과 확인 절차:

1단계: 호스트 서비스 실행 여부 확인

호스트에서 다음 명령을 실행합니다.

# MySQL 확인
sudo systemctl status mysql    # Linux
brew services list              # Mac

# 포트 수신 여부 확인
netstat -an | grep 3306
# 또는
lsof -i :3306

서비스가 실행 중이 아니라면 먼저 시작합니다.

2단계: 서비스 수신 주소 확인

호스트에서 다음 명령을 실행합니다.

# MySQL이 수신하는 주소 확인
sudo netstat -tlnp | grep 3306

출력은 다음과 비슷해야 합니다.

tcp  0  0 0.0.0.0:3306  0.0.0.0:*  LISTEN  1234/mysqld

세 번째 열을 확인하세요. 0.0.0.0:3306이라면 모든 주소를 수신하므로 문제가 없습니다. 127.0.0.1:3306이라면 바로 그 부분이 문제입니다. 로컬 연결만 수신하므로 컨테이너에서 연결할 수 없습니다.

앞의 ‘호스트 서비스 설정 시 주의할 점’ 절을 참고해 bind-address0.0.0.0으로 바꾸세요.

3단계: 방화벽 확인

테스트를 위해 방화벽을 잠시 비활성화합니다.

# Linux (ufw)
sudo ufw disable

# Linux (firewalld)
sudo systemctl stop firewalld

# Mac
# 시스템 설정 -> 개인정보 보호 및 보안 -> 방화벽 -> 끄기

방화벽을 끈 뒤 연결된다면 방화벽이 원인입니다. 앞에서 설명한 대로 방화벽 규칙을 설정한 다음 반드시 방화벽을 다시 활성화하세요.

문제 2: Connection timeout(연결 시간 초과)

오류 메시지는 다음과 같습니다.

Error: connect ETIMEDOUT host.docker.internal:3306

시간 초과는 연결 거부보다 원인을 찾기 어려울 수 있습니다. 패킷을 보냈지만 응답이 돌아오지 않았다는 뜻입니다.

가능한 원인과 확인 절차:

1단계: host.docker.internal이 해석되는지 확인

컨테이너 안에서 다음 명령을 실행합니다.

# 컨테이너 진입
docker exec -it your_container sh

# ping 테스트
ping host.docker.internal

ping에 실패하거나 ‘unknown host’가 표시된다면 host.docker.internal 설정이 올바르지 않은 것입니다.

Linux 사용자는 여기를 확인하세요: docker-compose.yml이나 docker run 명령에 --add-host=host.docker.internal:host-gateway를 추가했는지 확인합니다.

2단계: 포트 번호 확인

정말 3306 포트가 맞나요? MySQL 포트를 바꿨을 수도 있습니다.

호스트에서 다음 명령으로 확인합니다.

# MySQL 실제 포트 확인
sudo netstat -tlnp | grep mysqld

3단계: 컨테이너와 호스트 사이의 네트워크 연결 테스트

컨테이너 안에서 다음 명령을 실행합니다.

# 포트 연결 테스트
telnet host.docker.internal 3306

# 컨테이너에 telnet이 없으면 nc 사용
nc -zv host.docker.internal 3306

포트에 연결할 수 없다면 방화벽과 서비스 설정을 다시 확인합니다.

문제 3: Unknown host(host.docker.internal 해석 실패)

오류 메시지는 다음과 같습니다.

getaddrinfo ENOTFOUND host.docker.internal

DNS 해석에 실패해 컨테이너가 host.docker.internal이라는 도메인을 인식하지 못한다는 뜻입니다.

해결 방법:

컨테이너 설정을 확인하고 extra_hosts를 추가합니다.

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

또는 docker run 명령에 다음 옵션을 추가합니다.

docker run --add-host=host.docker.internal:host-gateway ...

문제 4: 인증 실패(Access denied)

오류 메시지는 다음과 같습니다.

Access denied for user 'root'@'172.17.0.2' (using password: YES)

MySQL 서버에는 연결했지만 사용자 권한이 올바르지 않다는 뜻입니다.

해결 방법:

MySQL 사용자에게 권한을 부여합니다.

-- 현재 사용자 권한 확인
SELECT user, host FROM mysql.user WHERE user='root';

-- root@localhost만 있다면 root@% 또는 [email protected].%를 생성해야 함
CREATE USER 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;

문제 5: 플랫폼마다 설정이 다름

팀의 Mac 사용자와 Linux 사용자가 같은 docker-compose.yml을 사용하는데 Mac에서는 실행되고 Linux에서는 실행되지 않는 상황입니다.

해결 방법:

모든 플랫폼에서 공통으로 사용할 수 있는 host-gateway 방식으로 통일합니다.

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Docker 버전이 20.10 이상인지 확인하세요. 팀원이 너무 오래된 Docker를 사용한다면 업그레이드하도록 안내합니다.

빠른 문제 해결 순서

연결 문제가 생기면 다음 순서로 확인합니다.

  1. 서비스가 실행 중인가?systemctl status / brew services list
  2. 수신 주소가 올바른가?netstat -tlnp에서 0.0.0.0인지 127.0.0.1인지 확인
  3. 컨테이너 설정을 추가했는가?extra_hosts 또는 --add-host 확인
  4. DNS가 작동하는가? → 컨테이너 안에서 ping host.docker.internal
  5. 포트에 연결되는가? → 컨테이너 안에서 telnet 또는 nc로 포트 테스트
  6. 방화벽이 차단하는가? → 잠시 비활성화해 테스트
  7. 권한을 부여했는가? → MySQL 사용자가 @localhost인지 @%인지 확인

십중팔구 처음 세 항목 중 하나가 원인입니다.

실제 적용 사례

원리를 살펴봤으니 이제 두 가지 실제 사례를 확인해 보겠습니다.

사례 1: Spring Boot 애플리케이션에서 호스트 MySQL 연결

상황: Spring Boot 프로젝트를 Docker로 실행하면서 로컬 MySQL 데이터베이스에 연결하려고 합니다.

1단계: Spring Boot 설정

application.yml:

spring:
  datasource:
    # host.docker.internal로 호스트 MySQL에 연결
    url: jdbc:mysql://host.docker.internal:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

2단계: Docker Compose 설정

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"  # 핵심 설정
    environment:
      # 환경 변수로 덮어쓸 수도 있음
      SPRING_DATASOURCE_URL: jdbc:mysql://host.docker.internal:3306/mydb
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: your_password

3단계: 호스트 MySQL 설정

/etc/mysql/mysql.conf.d/mysqld.cnf를 편집합니다.

[mysqld]
bind-address = 0.0.0.0

MySQL을 재시작합니다.

sudo systemctl restart mysql

사용자에게 권한을 부여합니다.

CREATE USER 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;

4단계: 실행 및 테스트

docker-compose up --build

HikariPool-1 - Start completed와 같은 로그가 표시되면 데이터베이스 연결에 성공한 것입니다.

문제 해결 기록:

처음 설정했을 때 Connection refused 오류가 발생했고 다음 순서로 해결했습니다.

  1. MySQL 실행 여부 확인: systemctl status mysql → 실행 중
  2. 수신 주소 확인: netstat -tlnp | grep 3306127.0.0.1:3306으로 설정된 것을 발견
  3. 설정 파일에서 bind-address = 0.0.0.0으로 변경한 뒤 MySQL 재시작
  4. 다시 실행하니 연결 성공

사례 2: Node.js 애플리케이션에서 호스트 Redis 연결

상황: Node.js 프로젝트에서 Redis를 캐시로 사용하며, 로컬 개발 시 Redis는 호스트에서 실행됩니다.

1단계: Node.js 코드

// redis-client.js
const redis = require('redis');

const client = redis.createClient({
  host: process.env.REDIS_HOST || 'host.docker.internal',
  port: process.env.REDIS_PORT || 6379,
  // Redis에 비밀번호가 설정된 경우
  password: process.env.REDIS_PASSWORD
});

client.on('connect', () => {
  console.log('Redis connected successfully');
});

client.on('error', (err) => {
  console.error('Redis error:', err);
});

module.exports = client;

2단계: Docker Compose 설정

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      NODE_ENV: development
      REDIS_HOST: host.docker.internal
      REDIS_PORT: 6379

3단계: 호스트 Redis 설정

/etc/redis/redis.conf 또는 /usr/local/etc/redis.conf를 편집합니다.

# bind 줄 찾기
bind 127.0.0.1 ::1

# 다음과 같이 변경
bind 0.0.0.0

Redis에서 protected-mode yes를 사용한다면 다음 설정도 변경해야 합니다.

protected-mode no  # 로컬 개발에서는 끌 수 있지만 프로덕션에서는 사용하지 마세요

Redis를 재시작합니다.

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

4단계: 확인

애플리케이션을 시작합니다.

docker-compose up

Redis connected successfully가 표시되면 완료입니다.

여러 플랫폼 처리:

팀에 Mac과 Linux 사용자가 함께 있다면 환경 변수를 공통으로 사용합니다.

const REDIS_HOST = process.env.REDIS_HOST || (
  process.platform === 'linux' ? 'host.docker.internal' : 'host.docker.internal'
);

잠깐, 이제는 모두 host.docker.internal을 사용할 수 있으므로 플랫폼을 구분할 필요가 없습니다. Docker Compose에 extra_hosts: ["host.docker.internal:host-gateway"]만 추가하면 Mac과 Linux에서 같은 설정을 사용할 수 있습니다.

사례 3: 전체 개발 환경 설정

애플리케이션 컨테이너에서 호스트의 MySQL과 Redis에 연결하는 실용적인 템플릿입니다.

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      # 데이터베이스 설정
      DB_HOST: host.docker.internal
      DB_PORT: 3306
      DB_NAME: myapp
      DB_USER: root
      DB_PASSWORD: your_password

      # Redis 설정
      REDIS_HOST: host.docker.internal
      REDIS_PORT: 6379

      # 애플리케이션 설정
      NODE_ENV: development
      PORT: 8080
    volumes:
      - .:/app
      - /app/node_modules  # node_modules는 마운트하지 않음
    command: npm run dev  # 개발 모드 핫 리로드

이에 맞는 호스트 설정 체크리스트입니다.

# MySQL
# /etc/mysql/mysql.conf.d/mysqld.cnf 편집
bind-address = 0.0.0.0
# 재시작: sudo systemctl restart mysql

# Redis
# /etc/redis/redis.conf 편집
bind 0.0.0.0
protected-mode no
# 재시작: sudo systemctl restart redis

# 방화벽(필요한 경우)
sudo ufw allow from 172.17.0.0/16 to any port 3306
sudo ufw allow from 172.17.0.0/16 to any port 6379

이 설정은 Mac과 Linux에서 모두 사용할 수 있으므로 그대로 복사해 적용하면 됩니다.

정리

내용이 길었지만 핵심은 세 가지입니다.

1. 원리 이해하기

컨테이너에는 자체 네트워크 공간이 있으므로 컨테이너 안의 localhost는 호스트가 아니라 컨테이너 자신을 가리킵니다. 이는 네트워크 네임스페이스 격리 구조에 따른 Docker의 설계이며 버그가 아닙니다.

2. 환경에 맞는 방법 선택하기

환경에 따라 다음과 같이 선택합니다.

환경권장 방법설정
Mac/Windows(Docker Desktop)host.docker.internal 바로 사용별도 설정 불필요
Linux(Docker Engine 20.10 이상)extra_hosts: host-gatewayDocker Compose 또는 —add-host
여러 플랫폼을 사용하는 팀extra_hosts: host-gateway모든 플랫폼에서 공통 설정 사용
구형 Linux 버전172.17.0.1 사용extra_hosts에 IP 지정
다른 방법이 모두 실패한 경우--network=host로컬 개발 전용이며 격리성을 해침

3. 서비스 올바르게 설정하기

컨테이너 설정만으로는 충분하지 않으며 호스트 서비스도 다음과 같이 설정해야 합니다.

  • 수신 주소를 0.0.0.0으로 변경
  • MySQL 사용자에게 Docker 네트워크 대역 접근 권한 부여
  • 방화벽에서 Docker 네트워크 대역 접근 허용

빠른 결정 트리

연결 문제가 발생하면 다음 순서로 확인합니다.

호스트 서비스에 연결할 수 없는가?

Mac/Windows와 Linux 중 어느 플랫폼을 사용하는가?

Mac/Windows:
  → host.docker.internal을 바로 사용
  → 그래도 연결되지 않으면 호스트 서비스 설정 확인

Linux:
  → Docker 버전이 20.10 이상인가?
      예 → extra_hosts: host-gateway 사용
      아니요 → extra_hosts: 172.17.0.1 사용
  → 호스트 서비스 설정 확인
  → 방화벽 확인

모두 시도해도 연결되지 않는가?
  → 문제 해결 체크리스트를 순서대로 확인
  → 최후의 방법: --network=host(로컬 개발에서만 사용)

마지막으로

컨테이너 기반 개발은 편리하지만 네트워크에는 함정이 많습니다. 그래도 host.docker.internalhost-gateway라는 두 가지 핵심 설정을 이해하면 대부분의 문제를 해결할 수 있습니다.

이 글을 저장해 두었다가 다음에 연결 문제가 생기면 바로 확인해 보세요. 팀원도 같은 문제로 어려움을 겪고 있다면 공유해도 좋습니다.

혹시 특이한 컨테이너 네트워크 문제를 겪었거나 더 나은 해결 방법을 알고 있다면 댓글로 남겨 주세요. 다른 사람에게 도움이 될 수 있습니다.

Docker 컨테이너에서 호스트에 접근하는 전체 설정 절차

host.docker.internal을 사용해 컨테이너에서 호스트 서비스에 접근하며, Mac, Windows, Linux의 설정 방법을 모두 다룹니다.

⏱️ Estimated time: 15 min

  1. 1

    Step 1: 문제의 원인과 해결 방법 이해하기

    문제의 원인: 컨테이너 안에서 localhost나 127.0.0.1로 호스트 서비스에 연결하면 항상 실패합니다. 컨테이너의 localhost는 호스트가 아니라 컨테이너 자신을 가리키므로 호스트 서비스에 접근하려면 별도 설정이 필요합니다.

    해결 방법: ‘마법의 도메인’인 host.docker.internal을 사용해 호스트에 접근합니다. 이 도메인은 호스트의 IP 주소로 자동 해석됩니다.

    플랫폼 지원:
    • Mac/Windows: Docker Desktop에서 기본 지원하므로 별도 설정이 필요 없습니다.
    • Linux: Docker 20.10 이상에서 --add-host 매개변수나 Docker Compose 설정을 사용해야 합니다.
  2. 2

    Step 2: Mac/Windows 플랫폼 설정하기

    Mac/Windows 설정 절차:

    1. host.docker.internal을 바로 사용합니다:
    docker run -e DATABASE_URL=host.docker.internal:3306 my-app

    2. 애플리케이션 설정에서 localhost를 바꿉니다:
    • 연결 문자열: host.docker.internal:3306
    • 환경 변수: DATABASE_HOST=host.docker.internal

    3. 연결을 확인합니다:
    • 컨테이너 안에서 테스트: docker exec -it container-name ping host.docker.internal
    • 애플리케이션 연결을 직접 테스트

    참고: Mac/Windows에서는 별도 설정이 필요 없으며 Docker Desktop이 자동으로 처리합니다.
  3. 3

    Step 3: Linux 플랫폼 설정 및 문제 해결하기

    Linux 설정 방법:

    1. Docker 버전 20.10 이상(권장):
    docker run --add-host=host.docker.internal:host-gateway my-app

    또는 docker-compose.yml에서:
    extra_hosts:
    - "host.docker.internal:host-gateway"

    2. Docker 버전 20.10 미만:
    docker run --add-host=host.docker.internal:172.17.0.1 my-app

    3. 전체 문제 해결 체크리스트:
    • 호스트에서 서비스가 실행 중인지 확인(netstat -tuln | grep 3306)
    • 포트가 열려 있는지 확인(telnet host.docker.internal 3306)
    • localhost 대신 host.docker.internal 사용
    • Linux에서 --add-host 매개변수 추가
    • 방화벽 설정 확인(iptables -L)
    • 서비스가 127.0.0.1이 아니라 0.0.0.0을 수신하는지 확인

FAQ

컨테이너 안에서 localhost로 호스트 서비스에 연결할 수 없는 이유는 무엇인가요?
컨테이너의 네트워크 격리 구조 때문입니다. 컨테이너는 독립된 네트워크 네임스페이스를 사용하므로 컨테이너 안의 localhost는 호스트가 아니라 컨테이너 자신(127.0.0.1)을 가리킵니다.

같은 물리적 컴퓨터에서 실행되더라도 별도의 네트워크 주소를 가진 두 집과 비슷합니다. 컨테이너가 localhost에 접근하면 호스트 네트워크가 아니라 컨테이너 내부 네트워크에 접근합니다.

해결 방법: host.docker.internal이라는 특수 도메인을 사용합니다. 이 도메인은 호스트의 IP 주소로 자동 해석되어 컨테이너가 호스트에서 실행 중인 서비스에 접근할 수 있게 합니다.
host.docker.internal은 모든 플랫폼에서 지원되나요?
플랫폼별 지원 범위:

• Mac/Windows(Docker Desktop): 별도 설정 없이 기본 지원
• Linux(Docker 20.10 이상): --add-host 매개변수를 직접 설정해야 함
• Linux(Docker 20.10 미만): 호스트 IP로 172.17.0.1을 사용해야 함

여러 플랫폼을 사용하는 팀이라면 Docker Compose의 extra_hosts를 공통 설정으로 사용하는 것이 좋습니다. 그러면 모든 플랫폼에서 같은 방식으로 작동합니다.
host.docker.internal을 설정해도 연결되지 않으면 어떻게 해야 하나요?
문제 해결 절차:

1. 호스트에서 서비스가 실행 중인지 확인합니다:
netstat -tuln | grep 포트번호

2. 서비스 수신 주소를 확인합니다:
서비스는 127.0.0.1만 수신하면 안 되며 0.0.0.0을 수신해야 합니다.

3. 네트워크 연결을 테스트합니다:
docker exec -it container-name ping host.docker.internal
docker exec -it container-name telnet host.docker.internal 포트번호

4. 방화벽 규칙을 확인합니다:
iptables -L(Linux)
Windows 방화벽 설정

5. 마지막 대안(개발 환경에서만 사용):
--network=host(컨테이너 격리성을 해칩니다.)
프로덕션 환경에서도 host.docker.internal을 사용해야 하나요?
권장하지 않습니다. 프로덕션 환경에서는 다음 방법을 사용해야 합니다.

• 서비스 이름(Docker Compose 네트워크)이나 컨테이너 이름 사용
• 명시적인 IP 주소 사용
• Consul, etcd 같은 서비스 디스커버리 사용

host.docker.internal은 주로 다음 용도에 적합합니다.
• 로컬 개발 환경
• 디버깅 및 테스트
• 데이터베이스나 Redis처럼 호스트에서 실행 중인 개발 도구 접근

프로덕션 환경에서 host.docker.internal을 사용하면 다음 문제가 생깁니다.
• 호스트 네트워크 설정에 의존
• 컨테이너화의 이점 감소
• 운영 복잡성 증가

5분 읽기 · 게시일: 2025년 12월 17일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog