Docker & Kubernetes 완전 정복 4편: Docker Compose로 멀티 컨테이너 관리
Docker & Kubernetes Complete Guide Part 4: Multi-Container Management with Docker Compose
서론: Docker Compose의 필요성
실제 애플리케이션은 대부분 여러 서비스로 구성됩니다. 웹 서버, 데이터베이스, 캐시, 메시지 큐 등 다양한 컴포넌트가 함께 작동해야 합니다. 이런 멀티 컨테이너 환경을 개별 docker run 명령으로 관리하는 것은 번거롭고 오류가 발생하기 쉽습니다.
Docker Compose는 이 문제를 해결하는 도구로, YAML 파일 하나로 여러 컨테이너를 정의하고 한 번의 명령으로 전체 애플리케이션 스택을 관리할 수 있게 해줍니다.
1. Docker Compose란?
1.1 Docker Compose의 정의
Docker Compose는 멀티 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구입니다. docker-compose.yml 파일에 애플리케이션의 서비스, 네트워크, 볼륨을 선언적으로 정의합니다.
Docker Compose의 주요 특징:
- 선언적 정의: YAML 파일로 전체 애플리케이션 스택을 정의
- 단일 명령 관리:
docker compose up으로 모든 서비스 시작 - 환경 격리: 프로젝트별로 독립적인 네트워크 생성
- 변수 지원: 환경 변수와
.env파일로 유연한 설정 - 개발 편의성: 로컬 개발 환경 구성에 특히 유용
1.2 Docker Compose V2
Docker Desktop과 최신 Docker Engine에는 Compose V2가 기본 포함됩니다. V2는 Go로 재작성되어 docker compose (하이픈 없음) 형식으로 사용합니다.
# V1 (레거시)
docker-compose up
# V2 (권장)
docker compose up
2. docker-compose.yml 구조
2.1 기본 구조
# docker-compose.yml
version: "3.9" # Compose 파일 버전 (선택적)
services: # 서비스(컨테이너) 정의
web:
image: nginx:alpine
ports:
- "80:80"
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes: # 볼륨 정의 (선택적)
db-data:
networks: # 네트워크 정의 (선택적)
backend:
2.2 최소 구성 예시
# 가장 간단한 docker-compose.yml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
3. 서비스 정의 상세
3.1 image - 이미지 지정
services:
web:
# Docker Hub 이미지
image: nginx:alpine
db:
# 레지스트리 이미지
image: registry.example.com/mydb:1.0
app:
# 특정 다이제스트
image: myapp@sha256:abc123...
3.2 build - 이미지 빌드
services:
app:
# 간단한 빌드
build: .
api:
# 상세 빌드 옵션
build:
context: ./api
dockerfile: Dockerfile.prod
args:
VERSION: "2.0"
BUILD_ENV: production
target: production # 멀티스테이지 빌드 타겟
frontend:
# 빌드 후 이미지 태그 지정
build: ./frontend
image: myapp/frontend:latest
3.3 ports - 포트 매핑
services:
web:
image: nginx
ports:
# 호스트:컨테이너
- "80:80"
- "443:443"
# 호스트 포트 자동 할당
- "80"
# 특정 IP에만 바인딩
- "127.0.0.1:3000:3000"
# UDP 포트
- "53:53/udp"
# 긴 형식
- target: 80
published: 8080
protocol: tcp
mode: host
3.4 volumes - 볼륨 마운트
services:
db:
image: postgres:15
volumes:
# 이름 있는 볼륨 (Named Volume)
- db-data:/var/lib/postgresql/data
# 바인드 마운트 (호스트 경로)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
# 읽기 전용 마운트
- ./config:/etc/app/config:ro
# 긴 형식
- type: volume
source: db-data
target: /var/lib/postgresql/data
volume:
nocopy: true
volumes:
db-data: # 볼륨 선언
cache-data:
driver: local
3.5 environment - 환경 변수
services:
app:
image: myapp
environment:
# Map 형식
NODE_ENV: production
DEBUG: "false"
DATABASE_URL: postgres://user:pass@db:5432/mydb
db:
image: postgres
environment:
# Array 형식
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
worker:
image: myworker
# 파일에서 환경 변수 로드
env_file:
- .env
- .env.local
3.6 command와 entrypoint
services:
app:
image: node:20
# 기본 명령 덮어쓰기
command: npm run dev
worker:
image: python:3.11
# 배열 형식
command: ["python", "-m", "celery", "worker"]
custom:
image: alpine
# 엔트리포인트 덮어쓰기
entrypoint: /custom-entrypoint.sh
command: ["--config", "/etc/app/config.yml"]
4. 의존성 관리 (depends_on)
4.1 기본 의존성
services:
web:
image: nginx
depends_on:
- api
- db
api:
image: myapi
depends_on:
- db
- redis
db:
image: postgres:15
redis:
image: redis:alpine
4.2 조건부 의존성 (service_healthy)
services:
web:
image: nginx
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
중요: depends_on은 시작 순서만 보장하며, 서비스가 "준비됨" 상태인지는 보장하지 않습니다. 프로덕션에서는 healthcheck와 함께 condition: service_healthy를 사용하세요.
5. 네트워크 설정
5.1 기본 네트워크 동작
Docker Compose는 프로젝트에 대해 기본 네트워크를 자동 생성합니다. 같은 네트워크의 서비스는 서비스 이름으로 서로 통신할 수 있습니다.
services:
web:
image: nginx
# db 서비스에 "db" 호스트명으로 접근 가능
db:
image: postgres
# web 서비스에 "web" 호스트명으로 접근 가능
5.2 커스텀 네트워크
services:
frontend:
image: nginx
networks:
- frontend-net
api:
image: myapi
networks:
- frontend-net
- backend-net
db:
image: postgres
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true # 외부 접근 차단
5.3 네트워크 별칭과 고정 IP
services:
db:
image: postgres
networks:
backend:
aliases:
- database
- postgres-primary
ipv4_address: 172.28.0.10
networks:
backend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
6. 실전 예제: WordPress + MySQL
# wordpress/docker-compose.yml
services:
wordpress:
image: wordpress:latest
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress_password
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress-data:/var/www/html
depends_on:
db:
condition: service_healthy
networks:
- wordpress-net
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress_password
MYSQL_ROOT_PASSWORD: root_password
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- wordpress-net
volumes:
wordpress-data:
db-data:
networks:
wordpress-net:
driver: bridge
7. 실전 예제: Node.js + MongoDB + Redis
# nodejs-stack/docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "3000:3000"
environment:
NODE_ENV: development
MONGODB_URI: mongodb://mongo:27017/myapp
REDIS_URL: redis://redis:6379
volumes:
- .:/app
- /app/node_modules
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-net
command: npm run dev
mongo:
image: mongo:7
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
MONGO_INITDB_DATABASE: myapp
volumes:
- mongo-data:/data/db
- ./mongo-init.js:/docker-entrypoint-initdb.d/init.js:ro
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- app-net
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- app-net
mongo-express:
image: mongo-express
restart: unless-stopped
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: admin
ME_CONFIG_MONGODB_ADMINPASSWORD: secret
ME_CONFIG_MONGODB_URL: mongodb://admin:secret@mongo:27017/
ME_CONFIG_BASICAUTH: "false"
depends_on:
- mongo
networks:
- app-net
volumes:
mongo-data:
redis-data:
networks:
app-net:
driver: bridge
8. Docker Compose 명령어
8.1 기본 명령어
# 서비스 시작 (백그라운드)
docker compose up -d
# 서비스 시작 (포그라운드, 로그 출력)
docker compose up
# 이미지 빌드 후 시작
docker compose up --build
# 특정 서비스만 시작
docker compose up -d web db
# 서비스 중지 및 제거
docker compose down
# 볼륨도 함께 제거
docker compose down -v
# 이미지도 함께 제거
docker compose down --rmi all
8.2 상태 확인 명령어
# 실행 중인 서비스 목록
docker compose ps
# 모든 서비스 (중지된 것 포함)
docker compose ps -a
# 서비스 로그 확인
docker compose logs
# 특정 서비스 로그
docker compose logs web
# 실시간 로그 추적
docker compose logs -f
# 최근 100줄만
docker compose logs --tail=100
# 타임스탬프 포함
docker compose logs -t
8.3 서비스 관리 명령어
# 서비스 재시작
docker compose restart
docker compose restart web
# 서비스 중지 (컨테이너 유지)
docker compose stop
# 중지된 서비스 시작
docker compose start
# 서비스 스케일링
docker compose up -d --scale web=3
# 설정 변경 적용
docker compose up -d --force-recreate
8.4 컨테이너 내 명령 실행 (exec)
# 컨테이너에서 명령 실행
docker compose exec web sh
docker compose exec db psql -U postgres
# 특정 사용자로 실행
docker compose exec --user root web sh
# 환경 변수 설정
docker compose exec -e DEBUG=true app npm test
# 새 컨테이너로 실행 (run)
docker compose run --rm app npm test
docker compose run --rm db psql -U postgres
9. 환경별 설정 분리
9.1 .env 파일 사용
# .env 파일
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
APP_PORT=3000
NODE_ENV=development
# docker-compose.yml
services:
app:
ports:
- "${APP_PORT}:3000"
environment:
NODE_ENV: ${NODE_ENV}
db:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
9.2 여러 Compose 파일 사용
# docker-compose.yml (기본 설정)
services:
app:
build: .
environment:
NODE_ENV: production
db:
image: postgres:15
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
# docker-compose.override.yml (개발 환경, 자동 적용)
services:
app:
build:
context: .
target: development
volumes:
- .:/app
environment:
NODE_ENV: development
ports:
- "3000:3000"
db:
ports:
- "5432:5432"
# docker-compose.prod.yml (프로덕션 환경)
services:
app:
restart: always
deploy:
replicas: 3
environment:
NODE_ENV: production
db:
restart: always
# 개발 환경 (기본 + override 자동 병합)
docker compose up
# 프로덕션 환경
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 여러 파일 명시적 지정
docker compose -f docker-compose.yml -f docker-compose.override.yml up
9.3 프로필 사용 (선택적 서비스)
services:
app:
image: myapp
db:
image: postgres:15
# 개발 환경에서만 사용
adminer:
image: adminer
profiles:
- debug
ports:
- "8080:8080"
# 테스트 환경에서만 사용
test-runner:
image: myapp-test
profiles:
- test
# 기본 서비스만 시작
docker compose up
# debug 프로필 포함
docker compose --profile debug up
# 여러 프로필
docker compose --profile debug --profile test up
10. 유용한 설정 옵션
10.1 restart 정책
services:
app:
image: myapp
restart: "no" # 재시작 안 함 (기본값)
# restart: always # 항상 재시작
# restart: on-failure # 실패 시에만 재시작
# restart: unless-stopped # 수동 중지 전까지 재시작
10.2 리소스 제한
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
reservations:
cpus: "0.25"
memory: 256M
10.3 로깅 설정
services:
app:
image: myapp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
결론
Docker Compose는 멀티 컨테이너 애플리케이션 관리를 위한 필수 도구입니다. 이 글에서 다룬 내용을 정리하면:
- docker-compose.yml 구조: services, volumes, networks의 역할과 정의 방법
- 서비스 정의: image, build, ports, volumes, environment 등 핵심 설정
- 의존성 관리: depends_on과 healthcheck를 활용한 안정적인 시작 순서
- 네트워크: 서비스 간 통신과 네트워크 격리
- 실전 예제: WordPress, Node.js 스택 등 실제 사용 사례
- 명령어: up, down, ps, logs, exec 등 주요 명령어
- 환경별 설정: .env 파일, override 파일, 프로필 활용
다음 편에서는 Kubernetes의 기본 개념과 아키텍처를 다루며, 컨테이너 오케스트레이션의 세계로 나아가겠습니다.