컨테이너 두 개는 서로를 어떻게 찾는가
웹 서버 컨테이너와 DB 컨테이너를 각각 띄우고 나면 바로 다음 질문이 생깁니다. 웹 서버는 DB에 어떻게 접속해야 할까요. localhost:5432 라고 쓰면 왜 안 될까요.
컨테이너는 기본적으로 자기만의 네트워크 세계에 있습니다
1장에서 언급했듯, 컨테이너는 리눅스의 network namespace로 격리됩니다. 각 컨테이너는 자기만의 네트워크 인터페이스와 자기만의 localhost 를 가집니다. 웹 서버 컨테이너 안에서 localhost:5432 는 그 웹 서버 컨테이너 자기 자신의 5432번 포트를 가리킬 뿐, DB 컨테이너와는 아무 상관이 없습니다. DB 컨테이너는 물리적으로 완전히 다른 네트워크 네임스페이스에 있는 별개의 "호스트"나 마찬가지입니다.
포트 매핑 — 컨테이너 밖에서 안으로 들어가는 문
컨테이너 바깥(호스트 머신이나 여러분의 브라우저)에서 컨테이너 안의 서비스에 접속하려면 포트를 매핑해야 합니다.
docker run -d -p 8080:80 nginx-p 8080:80 은 "호스트의 8080번 포트로 들어오는 트래픽을 컨테이너 안의 80번 포트로 전달하라"는 뜻입니다. 순서가 항상 호스트포트:컨테이너포트 라는 걸 기억해 두세요. 반대로 쓰는 실수가 은근히 잦습니다. 이제 브라우저에서 localhost:8080 에 접속하면 컨테이너 안의 nginx(80번 포트에서 대기 중)로 연결됩니다.
컨테이너끼리는 어떻게 서로를 찾는가 — 기본 브리지 네트워크의 한계
docker network 를 따로 만들지 않고 컨테이너를 띄우면, Docker는 기본 브리지 네트워크(bridge)에 컨테이너를 붙입니다. 여기서도 컨테이너끼리 서로 통신은 되지만(같은 브리지에 있으니 IP로는 접근 가능), 컨테이너 이름으로 서로를 찾는 DNS 기능은 기본 브리지에서 지원하지 않습니다. 매번 바뀌는 내부 IP를 알아내서 하드코딩해야 한다는 뜻인데, 컨테이너를 재시작하면 IP가 바뀌므로 실용적이지 않습니다.
그래서 실무에서는 사용자 정의 브리지 네트워크를 만들어서 씁니다.
docker network create my-app-net
docker run -d --name db --network my-app-net postgres:16
docker run -d --name web --network my-app-net \
-e DATABASE_URL=postgresql://db:5432/mydb \
my-web-image같은 사용자 정의 네트워크에 속한 컨테이너끼리는 컨테이너 이름을 그대로 호스트명으로 써서 서로를 찾을 수 있습니다. 위 예시에서 web 컨테이너 안에서 db 라는 이름으로 DNS 조회를 하면 Docker의 내장 DNS 서버가 그 시점의 db 컨테이너 IP로 풀어줍니다. 컨테이너가 재시작돼서 IP가 바뀌어도, 이름은 그대로이므로 연결 설정을 바꿀 필요가 없습니다.
sequenceDiagram participant Web as web 컨테이너 participant DNS as Docker 내장 DNS participant DB as db 컨테이너 Web->>DNS: "db"의 IP가 뭐야? DNS-->>Web: 172.18.0.3 Web->>DB: 172.18.0.3:5432로 접속
그래서 "localhost로 접속이 안 돼요"의 정체
이 장 서두의 질문으로 돌아가면, 답은 이렇습니다. DB에 접속하려는 코드가 컨테이너 밖(예: 로컬에서 실행하는 테스트 스크립트)에 있다면 localhost:호스트에매핑한포트 를 씁니다. 반대로 DB에 접속하려는 코드가 다른 컨테이너 안(예: web 컨테이너)에 있다면 localhost 가 아니라 DB 컨테이너의 이름을 호스트로 써야 하고, 포트도 컨테이너 내부 포트(매핑 전 원래 포트)를 씁니다. localhost 냐 db 냐는 "이 코드가 어느 네트워크 네임스페이스 안에서 실행되는가"에 달려 있습니다.
같은 이유로, 컨테이너 안에서 실행되는 코드가 호스트 머신에 떠 있는 서비스(예: 컨테이너화하지 않고 그냥 로컬에서 돌리는 개발용 DB)에 접속해야 할 때도 localhost 를 쓰면 안 됩니다. 이 경우 Docker Desktop 환경(Mac/Windows)에서는 host.docker.internal 이라는 특별한 DNS 이름이 호스트 머신을 가리키도록 준비되어 있습니다. 리눅스 네이티브 Docker에서는 이 이름이 기본적으로 지원되지 않다가, 최근 버전에서는 --add-host=host.docker.internal:host-gateway 옵션을 주면 동일하게 쓸 수 있습니다. Docker 버전에 따라 동작이 다를 수 있는 부분이라, 안 되면 버전부터 확인해보세요.
여러 컨테이너를 매번 docker network create 하고 docker run 을 길게 쳐서 띄우는 건 곧 번거로워집니다. 다음 장에서 다룰 docker compose는 이 네트워크 생성과 연결까지 전부 알아서 처리해 줍니다.