G: Docker Compose는 개발 환경 설정과 다중 컨테이너 애플리케이션 관리를 혁신적으로 단순화했습니다. 하지만 이 강력한 도구를 제대로 활용하지 못하면 오히려 복잡성을 증가시키거나 유지보수를 어렵게 만들 수 있습니다.
이번 글에서는 Docker Compose를 활용하여 프로젝트의 효율성과 안정성을 극대화할 수 있는 5가지 핵심 Best Practice를 소개합니다. 이 팁들을 적용하여 깔끔하고 견고한 컨테이너 기반 개발 환경을 구축해 보세요! 🚀
1. 개발/운영 환경 분리 (Separate Environments) 🔄✨
가장 기본적인 동시에 가장 중요한 Best Practice입니다. 개발 환경과 운영 환경은 요구사항이 매우 다릅니다. 개발 환경에서는 코드 변경 시 즉시 반영되어야 하고 (바인드 마운트), 디버깅 도구가 필요할 수 있으며, 데이터는 휘발적이어도 괜찮습니다. 반면 운영 환경에서는 안정성, 성능, 보안, 그리고 데이터 영속성이 최우선입니다.
어떻게 할까?
docker-compose.yml
파일 하나에 모든 것을 담기보다는, 공통 설정을 위한 기본 파일과 각 환경에 특화된 설정을 위한 오버라이드 파일을 분리하는 것이 좋습니다.
docker-compose.yml
: 개발 및 운영 환경에서 공통적으로 사용되는 기본 서비스 정의.docker-compose.dev.yml
: 개발 환경에 특화된 설정 (예: 바인드 마운트, 특정 포트, 디버깅 도구).docker-compose.prod.yml
: 운영 환경에 특화된 설정 (예: 스케일링, 리소스 제한, Nginx 프록시).
예시:
# docker-compose.yml (공통 설정)
version: '3.8'
services:
web:
build: .
ports:
- "80:80" # 기본적으로 80 포트 사용
environment:
APP_ENV: production # 기본은 production으로 설정
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data # 데이터 영속성 유지
volumes:
db_data:
# docker-compose.dev.yml (개발 환경 오버라이드)
version: '3.8'
services:
web:
build:
context: .
target: development # Multi-stage build를 사용하는 경우 개발 스테이지 지정
volumes:
- .:/app # 호스트 코드 변경 시 즉시 반영
- /app/node_modules # Node.js의 경우, 호스트 node_modules가 아닌 컨테이너 내부의 것을 사용
ports:
- "3000:80" # 개발 시에는 3000 포트 사용
environment:
APP_ENV: development
DEBUG_MODE: "true"
db:
environment:
POSTGRES_DB: myapp_dev # 개발용 데이터베이스 이름 분리
사용 방법:
- 개발 환경:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
- 운영 환경:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
(또는docker compose up -d
만 사용해도docker-compose.yml
이 기본으로 로드됨)
장점:
- 유연성: 각 환경의 요구사항에 맞춰 최적화된 설정을 쉽게 적용할 수 있습니다.
- 안전성: 개발 환경에서만 필요한 위험한 설정(예: 바인드 마운트)이 운영 환경으로 유출되는 것을 방지합니다.
- 간결성: 각 파일이 하나의 목적에 집중하여 가독성이 높아집니다.
2. 환경 변수와 .env
파일 활용 🔐⚙️
하드코딩된 값은 프로젝트 유지보수와 보안에 큰 걸림돌이 됩니다. 데이터베이스 연결 정보, API 키, 서비스 포트 등은 환경 변수로 관리하는 것이 Docker Compose의 모범 사례입니다.
어떻게 할까?
Docker Compose는 .env
파일을 자동으로 로드하여 여기에 정의된 변수들을 docker-compose.yml
파일에서 사용할 수 있도록 합니다. 민감한 정보는 environment
섹션에 직접 넣기보다는 .env
파일을 활용하거나, 더 나아가 Docker Secrets나 외부 시크릿 관리 도구를 사용하는 것이 좋습니다.
예시:
# .env 파일
DB_HOST=my_database
DB_USER=my_user
DB_PASSWORD=my_secure_password
APP_PORT=8080
# docker-compose.yml
version: '3.8'
services:
web:
image: myapp:latest
ports:
- "${APP_PORT}:${APP_PORT}" # .env 파일의 APP_PORT 변수 사용
environment:
- DB_HOST=${DB_HOST} # .env 파일의 DB_HOST 변수 사용
- DB_USER=${DB_USER}
# - DB_PASSWORD=${DB_PASSWORD} # 민감 정보는 가급적 직접 노출 피하고, 'secrets' 사용 권장
env_file:
- .env # 이 서비스를 위해 .env 파일을 로드합니다.
db:
image: postgres:13
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: myapp
참고:
.env
파일은 버전 관리 시스템(Git 등)에 포함하지 않는 것이 일반적입니다. 대신_template
이나_example
파일을 제공하여 어떤 변수들이 필요한지 알려줍니다.- 운영 환경에서는 Docker Secrets를 사용하여 민감 정보를 더욱 안전하게 관리하는 것이 좋습니다.
장점:
- 이식성: 환경별로 쉽게 설정을 변경할 수 있어 다른 환경으로의 배포가 용이합니다.
- 보안: 민감한 정보를 코드에 직접 노출하지 않아 보안 위험을 줄입니다.
- 유지보수: 설정 변경이 필요할 때
.env
파일만 수정하면 되므로 관리가 편리합니다.
3. Health Checks와 의존성 설정 ❤️🩹🔗
애플리케이션의 서비스들은 서로 의존하는 경우가 많습니다. 예를 들어, 웹 애플리케이션은 데이터베이스가 완전히 준비된 후에 시작되어야 합니다. 단순히 컨테이너가 ‘실행 중’인 것만으로는 충분하지 않습니다. 컨테이너 내부의 애플리케이션이 실제로 ‘건강하게 동작 중’인지 확인하는 것이 중요합니다.
어떻게 할까?
healthcheck
옵션을 사용하여 서비스의 건강 상태를 주기적으로 확인하고, depends_on
을 condition: service_healthy
와 함께 사용하여 서비스 간의 실제 의존성을 표현합니다.
예시:
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"] # DB 연결 테스트 명령어
interval: 5s # 5초마다 테스트
timeout: 5s # 테스트 타임아웃 5초
retries: 5 # 실패 시 5번 재시도
start_period: 10s # 컨테이너 시작 후 10초간은 건강 체크를 건너뜀 (초기화 시간)
volumes:
- db_data:/var/lib/postgresql/data # 데이터 영속성 유지
web:
build: .
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy # db 서비스가 'healthy' 상태가 될 때까지 기다림
# restart: on-failure # 컨테이너 실패 시 자동으로 재시작 (선택 사항)
장점:
- 견고성: 서비스 시작 순서의 문제로 인한 오류를 방지하고, 시스템 전체의 안정성을 높입니다.
- 복구력: 문제가 발생한 서비스를 자동으로 감지하고,
restart
정책과 결합하여 자체 복구 능력을 강화할 수 있습니다. - 명확성: 서비스의 실제 상태를 파악하기 용이해집니다.
4. Named Volumes을 이용한 데이터 영속성 💾🌱
컨테이너는 기본적으로 휘발성입니다. 컨테이너가 제거되면 그 안에 있던 모든 데이터도 함께 사라집니다. 데이터베이스, 로그 파일, 업로드된 파일 등 중요한 데이터는 컨테이너의 생명주기와 독립적으로 유지되어야 합니다. 바인드 마운트도 사용 가능하지만, 프로덕션 환경에서는 Named Volumes이 더 권장됩니다.
어떻게 할까?
volumes
섹션을 통해 명명된 볼륨을 정의하고, 각 서비스의 volumes
섹션에서 이 볼륨을 컨테이너 내부의 경로에 마운트합니다.
예시:
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data # 'db_data'라는 이름의 볼륨을 마운트
app:
build: .
ports:
- "80:80"
volumes:
- app_logs:/var/log/myapp # 'app_logs' 볼륨을 앱 로그 경로에 마운트
volumes:
db_data: # db_data 볼륨 정의
app_logs: # app_logs 볼륨 정의
driver: local # 명시적으로 로컬 드라이버 사용 (선택 사항)
장점:
- 데이터 무결성: 컨테이너가 삭제되거나 재생성되어도 데이터는 안전하게 보존됩니다.
- 백업 및 복구: 볼륨 데이터는 호스트 시스템에 저장되므로 백업 및 복구가 용이합니다.
- 관리 용이성: Named Volumes은 Docker CLI 명령어로 쉽게 관리(목록 확인, 삭제 등)할 수 있습니다.
- 성능: 바인드 마운트보다 일반적으로 더 좋은 성능을 제공합니다.
5. 사용자 정의 네트워크 사용 🌐🛣️
Docker Compose는 기본적으로 모든 서비스에 대해 하나의 default
브릿지 네트워크를 생성합니다. 하지만 서비스가 많아지거나, 보안상 분리가 필요할 때, 혹은 더 명확한 아키텍처를 원할 때는 사용자 정의 네트워크를 사용하는 것이 좋습니다.
어떻게 할까?
networks
섹션을 정의하고, 각 서비스의 networks
섹션에서 해당 네트워크에 참여하도록 설정합니다. 동일한 네트워크에 속한 서비스들은 서로 서비스 이름(hostname)으로 통신할 수 있습니다.
예시:
version: '3.8'
services:
web:
build: .
ports:
- "80:80"
networks:
- myapp_frontend # 프론트엔드 네트워크에 참여
- myapp_backend # 백엔드 네트워크에도 참여 (필요하다면)
api:
build: ./api
networks:
- myapp_backend # 백엔드 네트워크에 참여
environment:
DB_HOST: db # 같은 네트워크 내에서 서비스 이름으로 DB 접근 가능
WEB_APP_HOST: web # 같은 네트워크 내에서 서비스 이름으로 웹 앱 접근 가능
db:
image: postgres:13
networks:
- myapp_backend # 백엔드 네트워크에 참여
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
networks:
myapp_frontend:
driver: bridge # 명시적으로 브릿지 드라이버 사용
myapp_backend:
driver: bridge
장점:
- 서비스 격리: 서로 다른 네트워크에 있는 서비스는 기본적으로 통신할 수 없으므로 보안성이 향상됩니다.
- 향상된 서비스 디스커버리: 동일 네트워크 내에서는 서비스 이름으로 쉽게 통신할 수 있습니다.
- 아키텍처 명확성: 애플리케이션의 논리적 구조를 네트워크를 통해 시각적으로 표현할 수 있습니다.
- IP 주소 관리: 필요한 경우 각 네트워크에 고정 IP 서브넷을 할당할 수 있습니다.
결론 🐳✨
Docker Compose는 강력한 도구이지만, 이 5가지 Best Practice를 적용하면 그 잠재력을 최대한 끌어올릴 수 있습니다. 개발 환경의 유연성부터 데이터의 영속성, 그리고 시스템의 견고함까지, 이 팁들은 여러분의 컨테이너 기반 프로젝트를 한 단계 성장시킬 것입니다.
지금 바로 여러분의 docker-compose.yml
파일에 이 팁들을 적용해 보세요! Happy Dockering! 🚀