[Docker 시리즈 8편] 포트·네트워크·서비스 이름 완전정복

English version

7편에서 dev/prod 컨테이너를 구분했다면, 이번 글은 그 컨테이너들이 실제로 어떤 경로를 통해 통신하는지 정리합니다. 목표는 단순합니다. 호스트 포트, 컨테이너 포트, 서비스 이름을 명확히 구분해 머릿속에 그림을 그리는 것. 예제는 nginx 웹, Node API, PostgreSQL DB 세 컨테이너를 사용합니다.

이 글의 흐름

  1. 호스트/컨테이너/서비스 이름을 구분하는 머릿속 다이어그램
  2. 웹·API·DB Compose 스니펫으로 포트와 네트워크 읽기
  3. 서비스 이름으로 내부 DNS가 어떻게 작동하는지 이해하기
  4. Mini Lab: curl과 ping으로 통신 경로 확인하기
  5. 가장 많이 헷갈리는 주소를 한 번에 정리하기

읽기 카드

  • 예상 소요 시간: 17분
  • 사전 준비: docker compose up과 포트 개념 기초
  • 읽고 나면: 포트 매핑과 서비스 이름을 초보자에게 직접 설명할 수 있습니다.

머릿속 다이어그램

아래 텍스트 다이어그램을 계속 떠올리면 헷갈릴 일이 거의 없습니다.

[브라우저] --localhost:8080--> [호스트 OS] --포트 매핑--> [web 컨테이너 80]
                                               |
                                               +--> 같은 네트워크의 다른 컨테이너 (service name)
  • 왼쪽(브라우저)은 항상 localhost + 공개 포트로 접속합니다.
  • 가운데(포트 매핑)는 ports: "8080:80"처럼 "호스트
    " 순서로 연결합니다.
  • 오른쪽(컨테이너)에서는 서비스 이름(api, db 등)을 DNS처럼 사용해 서로 통신합니다.

세 구간을 마음속으로 구분하면 808080, api:4000localhost:4000 같은 주소가 헷갈리지 않습니다.

웹·API·DB Compose 스니펫

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./public:/usr/share/nginx/html:ro
    depends_on:
      - api
    networks:
      - app-net

  api:
    build: ./api
    environment:
      - DATABASE_URL=postgres://postgres:your-db-password@db:5432/app
    ports:
      - "4000:4000"
    networks:
      - app-net

  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=your-db-password
      - POSTGRES_DB=app
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-net

volumes:
  db-data:

networks:
  app-net:
    driver: bridge
  • web은 외부 8080을 내부 80에 연결하고, 정적 파일만 읽어 옵니다.
  • api는 외부에서도 디버깅할 수 있도록 4000:4000을 열어 두었습니다.
  • db포트 매핑이 없습니다. 즉, 호스트에서 바로 접근할 수 없고 같은 네트워크 컨테이너만 db:5432로 접속할 수 있습니다.
  • DATABASE_URLdb:5432가 등장합니다. 여기서 db는 컨테이너 이름이 아니라 서비스 이름 기반 DNS 기록입니다.

추가로, api 컨테이너 안의 서버 프로그램은 보통 0.0.0.0에 바인딩되어 있어야 다른 컨테이너나 호스트에서 접근할 수 있습니다. 초보자들이 localhost에만 바인딩해 두고 "왜 포트를 열었는데도 안 되지?" 하고 헷갈리는 경우가 많습니다.

서비스 이름 = 내부 DNS

Compose는 같은 네트워크에 묶인 서비스마다 service-name 호스트를 자동으로 만들어 줍니다.

하고 싶은 일 사용하는 주소
브라우저에서 웹 확인 http://localhost:8080
호스트에서 API 호출 http://localhost:4000
web 컨테이너가 API 호출 http://api:4000
API 컨테이너가 DB 접속 postgres://...@db:5432/app

localhost는 언제나 내 컴퓨터를 가리킵니다. 컨테이너끼리 서로 부르는 이름은 서비스 이름입니다. 두 주소를 혼동하면 대부분의 통신 오류가 발생합니다.

즉, 호스트에서는 localhost, 같은 Compose 네트워크 안에서는 서비스 이름을 쓴다고 생각하면 됩니다. 호스트에서 api:4000처럼 바로 접속할 수는 없습니다.

Mini Lab: 통신 경로 확인하기

  1. 위 Compose 파일을 저장하고 docker compose up -d를 실행합니다.
  2. curl localhost:8080으로 웹 정적 파일이 보이는지 확인합니다.
  3. curl localhost:4000/health으로 API가 응답하는지 확인합니다.
  4. docker compose exec web ping -c 1 api를 실행해 web 컨테이너에서 API를 서비스 이름으로 찾을 수 있는지 확인합니다.
  5. docker compose exec api sh -c "nc -vz db 5432"로 API 컨테이너가 DB 포트에 접속 가능한지 테스트합니다.
  6. docker compose down으로 정리합니다.

이 과정을 반복하면 "내가 지금 어디에서 어디로 접속하는지"를 자연스럽게 설명할 수 있게 됩니다.

실패 예시도 한 번 보면 더 오래 기억됩니다.

  • docker compose exec web curl http://localhost:4000/health를 실행하면, 많은 경우 web 컨테이너 자기 자신localhost를 바라보기 때문에 API에 닿지 못합니다.
  • 반대로 curl http://api:4000/health처럼 서비스 이름을 쓰면 같은 네트워크 안에서 API 컨테이너를 찾을 수 있습니다.

즉, 컨테이너 안에서의 localhost는 "내 맥"이 아니라 "그 컨테이너 자신"입니다.

정리하면 범위가 이렇게 나뉩니다.

  • 컨테이너 안의 localhost: 그 컨테이너 자신의 루프백 주소
  • 다른 컨테이너 접근: 같은 네트워크의 서비스 이름 사용
  • 호스트 머신 접근: 공개된 포트의 localhost, 또는 Docker Desktop 계열에서는 host.docker.internal 같은 별도 이름 사용

가장 많이 헷갈리는 주소를 한 번에 정리하기

  • 브라우저에서 접속할 때: http://localhost:8080
  • web 컨테이너가 api를 부를 때: http://api:4000
  • api 컨테이너가 db에 붙을 때: db:5432

즉, 호스트에서는 localhost, 컨테이너끼리는 서비스 이름을 쓴다는 점만 분명히 기억하면 됩니다. 다음 글(9편)에서는 이 통신 경로가 잘 유지되는지 확인하기 위한 헬스체크와 재시작 정책을 다룰 예정입니다.

💬 댓글

이 글에 대한 의견을 남겨주세요