Docker Compose 네트워크와 볼륨 — 서비스 간 통신과 데이터 공유
Docker Compose에서 서비스 이름만으로 다른 서비스에 접근할 수 있는 건 마법이 아닙니다. 내부에서 어떤 네트워크와 DNS 메커니즘이 동작하는지 이해하면 더 유연한 구성이 가능합니다.
Compose의 기본 네트워크
네트워크를 따로 선언하지 않으면, Docker Compose는 자동으로 default 네트워크를 생성합니다.
# compose.yaml
services:
api:
image: myapi:latest
db:
image: postgres:16-alpine
docker compose up -d
# 자동 생성된 네트워크 확인
docker network ls
# NETWORK ID NAME DRIVER
# abc123 myproject_default bridge
# 서비스 이름으로 통신 가능
docker compose exec api ping db
# PING db (172.20.0.3): 56 data bytes...
프로젝트 이름(myproject)은 디렉토리 이름 또는 COMPOSE_PROJECT_NAME에서 결정됩니다.
서비스 DNS 해석
Compose의 각 서비스는 서비스 이름으로 DNS 해석이 됩니다.
services:
api:
image: myapi:latest
environment:
# 서비스 이름을 호스트로 사용
DB_HOST: db
REDIS_HOST: redis
CACHE_URL: redis://redis:6379
db:
image: postgres:16-alpine
redis:
image: redis:7-alpine
애플리케이션 코드에서 db, redis라는 호스트명을 그대로 사용할 수 있습니다. Docker의 내장 DNS 서버(127.0.0.11)가 서비스 이름을 해당 컨테이너의 IP로 해석합니다.
스케일링과 DNS
# api 서비스를 3개로 스케일링
docker compose up -d --scale api=3
# 'api'로 DNS 질의 시 모든 인스턴스의 IP 반환
docker compose exec db nslookup api
# Name: api
# Address: 172.20.0.4
# Address: 172.20.0.5
# Address: 172.20.0.6
기본적으로 라운드 로빈 방식으로 IP가 반환되어 간단한 로드 밸런싱이 됩니다. 하지만 클라이언트 측 DNS 캐싱에 주의해야 합니다.
네트워크 별칭
services:
postgres-primary:
image: postgres:16-alpine
networks:
backend:
aliases:
- db
- database
- primary-db
networks:
backend:
db, database, primary-db 어떤 이름으로든 접근할 수 있습니다.
다중 네트워크 구성
기본 패턴: 프론트엔드/백엔드 분리
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
networks:
- frontend
api:
image: myapi:latest
networks:
- frontend # nginx에서 접근 가능
- backend # db에 접근 가능
worker:
image: myworker:latest
networks:
- backend # db에 접근 가능, 외부 접근 불가
db:
image: postgres:16-alpine
networks:
- backend # api와 worker만 접근 가능
redis:
image: redis:7-alpine
networks:
- backend
networks:
frontend:
backend:
외부 트래픽
│
↓
┌─ frontend ─────────────┐
│ nginx ──→ api │
└───────────┼─────────────┘
│
┌─ backend ─┼─────────────┐
│ api ──→ db │
│ worker ──→ db │
│ api ──→ redis │
│ worker ──→ redis │
└─────────────────────────┘
nginx에서 db로 직접 접근은 불가능합니다. 서로 다른 네트워크에 있기 때문입니다.
네트워크 설정 옵션
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
gateway: 172.28.0.1
backend:
driver: bridge
internal: true # 외부 인터넷 접근 차단
internal: true로 설정하면 해당 네트워크의 컨테이너는 외부 인터넷에 접근할 수 없습니다. 보안이 중요한 백엔드 네트워크에 유용합니다.
다른 Compose 프로젝트와 네트워크 공유
서로 다른 Compose 파일(프로젝트)의 서비스가 통신해야 할 때 external 네트워크를 사용합니다.
프로젝트 A: 공유 인프라
# infra/compose.yaml
services:
db:
image: postgres:16-alpine
networks:
- shared
redis:
image: redis:7-alpine
networks:
- shared
networks:
shared:
name: shared-infra # 명시적 네트워크 이름
프로젝트 B: 애플리케이션
# app/compose.yaml
services:
api:
image: myapi:latest
environment:
DB_HOST: db
REDIS_HOST: redis
networks:
- shared-infra
networks:
shared-infra:
external: true # 이미 존재하는 네트워크 사용
# 순서대로 실행
cd infra && docker compose up -d
cd ../app && docker compose up -d
# app의 api가 infra의 db, redis에 접근 가능
external: true는 Compose가 네트워크를 생성/삭제하지 않고, 이미 존재하는 네트워크를 참조합니다.
볼륨 공유 패턴
서비스 간 볼륨 공유
services:
# API가 생성한 파일을 Nginx가 서빙
api:
image: myapi:latest
volumes:
- static-files:/app/static
nginx:
image: nginx:1.25-alpine
volumes:
- static-files:/usr/share/nginx/html/static:ro
volumes:
static-files:
데이터 초기화 패턴
PostgreSQL, MySQL 등의 공식 이미지는 /docker-entrypoint-initdb.d/ 디렉토리의 스크립트를 최초 실행 시 자동으로 실행합니다.
services:
db:
image: postgres:16-alpine
volumes:
- pg-data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
pg-data:
-- db/init/01-schema.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
-- db/init/02-seed.sql
INSERT INTO users (name, email) VALUES
('테스트 사용자', 'test@example.com');
파일은 이름 순으로 실행됩니다. 번호 접두사를 붙여 실행 순서를 보장하세요.
주의: 초기화 스크립트는 볼륨이 비어있을 때(최초 실행)만 실행됩니다. 이미 데이터가 있으면 무시됩니다.
외부 볼륨 사용
services:
api:
volumes:
- important-data:/data
volumes:
important-data:
external: true # docker volume create important-data로 미리 생성
external: true 볼륨은 docker compose down -v로도 삭제되지 않아 중요한 데이터를 보호합니다.
볼륨 드라이버 설정
volumes:
nfs-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,rw
device: ":/shared/data"
tmpfs-cache:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: size=100m
다중 Compose 파일 간 볼륨 공유
# 프로젝트 A
volumes:
shared-uploads:
name: app-uploads # 명시적 이름
# 프로젝트 B
volumes:
uploads:
external: true
name: app-uploads # 같은 이름으로 참조
실전: 마이크로서비스 네트워크 구성
services:
gateway:
image: nginx:1.25-alpine
ports:
- "80:80"
networks:
- public
user-service:
image: user-svc:latest
networks:
- public
- user-db-net
- messaging
order-service:
image: order-svc:latest
networks:
- public
- order-db-net
- messaging
user-db:
image: postgres:16-alpine
volumes:
- user-db-data:/var/lib/postgresql/data
networks:
- user-db-net
order-db:
image: postgres:16-alpine
volumes:
- order-db-data:/var/lib/postgresql/data
networks:
- order-db-net
rabbitmq:
image: rabbitmq:3.13-management-alpine
networks:
- messaging
networks:
public:
user-db-net:
internal: true
order-db-net:
internal: true
messaging:
internal: true
volumes:
user-db-data:
order-db-data:
각 서비스가 필요한 네트워크에만 연결되어 있어서 보안이 강화됩니다.
트러블슈팅
# 네트워크 상태 확인
docker network ls
docker network inspect myproject_default
# 서비스 간 DNS 해석 테스트
docker compose exec api nslookup db
docker compose exec api wget -qO- http://other-service:8080/health
# 연결된 네트워크 확인
docker inspect mycontainer --format '{{json .NetworkSettings.Networks}}' | jq
# 네트워크 재생성 (문제 해결 시)
docker compose down
docker compose up -d
정리
- Docker Compose는 자동으로 default 네트워크를 생성하고, 서비스 이름으로 DNS 해석이 가능합니다.
- 다중 네트워크로 프론트엔드/백엔드를 분리하여 보안을 강화합니다.
internal: true로 외부 접근을 차단할 수 있습니다. - 다른 Compose 프로젝트와 통신이 필요하면
external: true네트워크를 공유합니다. - 데이터베이스 초기화는
/docker-entrypoint-initdb.d/에 SQL/쉘 스크립트를 마운트합니다 (최초 실행 시에만 동작). - 중요한 데이터의 볼륨은
external: true로 선언하여docker compose down -v로부터 보호합니다.