MMeshOps
Docker와 Docker Compose: 개발 환경을 하나로 통일하는 도구
11예상 10입문

컨테이너 간의 통신: 도커 네트워크

Next.js에서 API 서버로, API 서버에서 DB로 컨테이너끼리 어떻게 이름을 부르며 통신하는지 도커 내부 네트워크의 원리를 알아봅니다.

컨테이너들은 기본적으로 완전히 격리된 공간입니다. 그렇다면 프론트엔드 컨테이너는 어떻게 백엔드 컨테이너와 통신하고, 백엔드 컨테이너는 어떻게 데이터베이스에 접근할 수 있을까요? 이번 에피소드에서는 도커 네트워크의 개념을 알아보고, IP 주소를 외울 필요 없이 서비스 이름만으로 컨테이너끼리 대화하는 방법을 학습합니다.

격리된 컨테이너들을 연결하는 다리, 네트워크

도커의 가장 큰 특징 중 하나는 각 컨테이너가 독립적인 환경을 가진다는 것입니다. 하지만 실제 서비스는 프론트엔드, 백엔드, 데이터베이스 등 여러 애플리케이션이 상호작용하며 동작합니다. 이들이 서로 통신하려면 안전하고 효율적인 연결 통로가 필요합니다. 이것이 바로 도커 네트워크(Docker Network)의 역할입니다.

단일 도커 환경에서 컨테이너를 실행할 때는 수동으로 네트워크를 생성하고 컨테이너를 그 네트워크에 연결해야 했습니다. 하지만 복잡한 명령어를 계속 입력하는 것은 비효율적입니다. 이 문제를 깔끔하게 해결해 주는 도구가 바로 이전에 배운 Docker Compose입니다.

도커 컴포즈 네트워크의 마법

Docker Compose는 `docker-compose.yml` 파일을 실행할 때, 내부적으로 기본 네트워크(default network)를 자동으로 생성합니다. 그리고 파일에 정의된 모든 서비스(컨테이너)를 이 기본 네트워크에 연결합니다.

이 자동화된 네트워크 환경에서는 매우 강력한 기능이 제공됩니다. 바로 내부 DNS(Domain Name System) 기능입니다. 일반적인 네트워크 통신을 위해서는 대상의 IP 주소를 알아야 하지만, 도커 컴포즈 환경에서는 컨테이너의 IP 주소를 전혀 알 필요가 없습니다.

대신 서비스 이름(Service Name)을 그대로 도메인 주소처럼 사용할 수 있습니다.

서비스 이름 기반의 도커 네트워크 통신

Next.js 컨테이너

http://api:8080 호출

Go API 컨테이너

Go API 컨테이너

postgres:5432 연결

PostgreSQL 컨테이너

서비스 이름으로 통신하기

실제 환경에서 어떻게 동작하는지 예를 들어보겠습니다. 만약 `api`라는 이름으로 백엔드 서비스를 정의하고, `db`라는 이름으로 데이터베이스 서비스를 정의했다면, `api` 컨테이너 내부의 코드는 데이터베이스 주소로 `localhost`나 복잡한 `172.x.x.x` 같은 IP 주소가 아닌, 그저 `db`라는 문자를 사용하면 됩니다.

도커 내부 DNS 서버가 `db`라는 요청을 받으면 자동으로 해당 데이터베이스 컨테이너의 실제 IP 주소로 연결해 줍니다. 컨테이너가 재시작되어 IP 주소가 변경되더라도 도커가 알아서 DNS 정보를 갱신해주므로 개발자는 IP 주소 관리에 신경 쓸 필요가 없습니다.

컨테이너 내부 통신과 외부 통신의 차이

Next.js 프론트엔드 코드(컨테이너 내부 실행)에서 백엔드를 호출할 때의 주소와, 사용자 브라우저(컨테이너 외부)에서 호출할 때의 주소는 다릅니다.

  • 컨테이너 내부 통신 (서버 사이드 렌더링 등): `http://api:8080/data` (도커 내부 네트워크 사용)
  • 컨테이너 외부 통신 (사용자 브라우저에서 Fetch): `http://localhost:8080/data` (포트 매핑된 호스트 주소 사용)

이 두 가지 통신 경로의 차이를 명확히 인지하는 것이 로컬 스택 개발에서 가장 중요합니다.

호스트와 컨테이너 통신 분리하기

많은 초보자들이 혼동하는 부분은 바로 포트 매핑(`ports`)과 내부 통신의 관계입니다. `ports: - "8080:8080"` 설정은 도커 외부(내 컴퓨터)에서 도커 내부로 들어오는 문을 열어주는 역할만 합니다.

컨테이너끼리 도커 내부 네트워크를 통해 통신할 때는 이 포트 매핑 설정이 전혀 필요하지 않습니다. 도커 내부 네트워크에 함께 연결된 컨테이너들은 포트 매핑 여부와 상관없이 서로의 모든 내부 포트에 접근할 수 있습니다.

안전한 데이터베이스 보호 전략

이 원리를 이용하면 데이터베이스를 훨씬 안전하게 보호할 수 있습니다. 데이터베이스 컨테이너에 `ports` 설정을 아예 작성하지 않으면, 호스트 컴퓨터나 외부에서는 데이터베이스에 직접 접근할 수 없게 됩니다. 오직 도커 내부 네트워크에 속한 백엔드 컨테이너만이 `db:5432`라는 주소로 접근할 수 있게 되는 것입니다.

물론 로컬 개발 환경에서는 데이터베이스 툴(예: DBeaver, DataGrip)을 이용해 데이터를 직접 확인해야 하므로 포트를 열어두는 것이 편리합니다. 하지만 운영 환경에서는 외부 접근을 차단하고 내부 네트워크만 사용하는 것이 기본적인 보안 원칙입니다.

통신 방식 요약

통신 방식도메인 주소특징
컨테이너 간 내부 통신서비스 이름 (예: `db`, `api`)포트 매핑 불필요, 빠르고 안전함
호스트에서 컨테이너 통신localhost 또는 127.0.0.1포트 매핑(ports) 필수, 외부 접근용

사용자 정의 네트워크 (Custom Network)

기본 네트워크 외에도 필요에 따라 직접 네트워크를 정의하여 컨테이너들을 논리적으로 분리할 수도 있습니다. 예를 들어, 웹 서비스들만 연결된 `frontend_net`과 데이터베이스 서버만 연결된 `backend_net`을 분리하고, API 서버만 두 네트워크 양쪽에 다리를 걸치게 만들어 보안을 한층 더 강화할 수 있습니다.

services:
  web:
    image: nextjs-app
    networks:
      - frontend_net
  api:
    image: go-api
    networks:
      - frontend_net
      - backend_net
  db:
    image: postgres
    networks:
      - backend_net

networks:
  frontend_net:
  backend_net:

위와 같이 설정하면 `web` 컨테이너는 `db` 컨테이너와 아예 통신할 수 없게 되어 아키텍처의 의도치 않은 접근을 원천적으로 막을 수 있습니다.

개념 퀴즈

퀴즈 답을 맞춰야 학습 완료가 됩니다.

Docker Compose 환경에서 프론트엔드 컨테이너가 백엔드 컨테이너(서비스 이름: backend)와 통신하려고 할 때, 올바른 주소 형식은 무엇일까요?

3줄 요약

  1. 1도커 컴포즈는 파일 내의 서비스들을 하나로 묶는 가상의 기본 네트워크를 자동 생성합니다.
  2. 2컨테이너들은 서로의 IP 주소 대신, `api`나 `db` 같은 서비스 이름을 사용하여 통신할 수 있습니다.
  3. 3컨테이너 간 내부 통신에는 포트 매핑(`ports`) 설정이 필요 없으며, 이는 외부 접근을 위해 사용됩니다.