Docker Compose 기초 — 여러 컨테이너를 하나의 파일로 관리하기
웹 서버, API 서버, 데이터베이스, 캐시를 각각
docker run으로 실행하고 네트워크와 볼륨을 수동으로 연결하면 명령어가 너무 길어집니다. 이걸 하나의 파일로 정의할 수는 없을까요?
Docker Compose란
Docker Compose는 여러 컨테이너로 구성된 애플리케이션을 하나의 YAML 파일로 정의하고 관리 하는 도구입니다.
# compose.yaml
services:
web:
image: nginx:1.25-alpine
ports:
- "80:80"
api:
build: ./api
ports:
- "8080:8080"
environment:
DB_HOST: db
최상위 volumes와 networks 섹션에서 서비스 간 공유 리소스를 정의합니다.
db:
image: postgres:16-alpine
volumes:
- pg-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
pg-data:
# 모든 서비스 시작
docker compose up -d
# 모든 서비스 중지 및 컨테이너 삭제
docker compose down
# 볼륨까지 삭제
docker compose down -v
compose.yaml 핵심 문법
services
서비스는 하나의 컨테이너를 정의합니다.
services:
# 서비스 이름 (DNS 이름으로도 사용됨)
api:
# 이미지 사용
image: myapi:latest
# 또는 빌드
build:
context: ./api
dockerfile: Dockerfile
args:
NODE_ENV: production
나머지 설정을 이어서 정의합니다.
# 포트 매핑
ports:
- "8080:8080" # 호스트:컨테이너
- "127.0.0.1:9229:9229" # 특정 인터페이스
# 환경변수
environment:
- NODE_ENV=production
- DB_HOST=db
최상위 volumes와 networks 섹션에서 서비스 간 공유 리소스를 정의합니다.
# 또는 map 형태
environment:
NODE_ENV: production
DB_HOST: db
# 볼륨
volumes:
- ./src:/app/src # 바인드 마운트
- node_modules:/app/node_modules # 이름 있는 볼륨
# 리소스 제한
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
# 재시작 정책
restart: unless-stopped
networks
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
네트워크를 명시하지 않으면 프로젝트명_default 네트워크가 자동 생성되어 모든 서비스가 연결됩니다.
volumes
services:
db:
volumes:
- pg-data:/var/lib/postgresql/data
volumes:
pg-data: # 기본 local 드라이버
# 외부 볼륨 사용
shared-data:
external: true # docker volume create로 미리 생성된 볼륨
depends_on과 서비스 시작 순서
기본 사용법
services:
api:
depends_on:
- db
- redis
# db와 redis 컨테이너가 "시작된 후"에 api가 시작됨
db:
image: postgres:16-alpine
redis:
image: redis:7-alpine
하지만 depends_on은 컨테이너의 시작 만 보장합니다. 데이터베이스가 실제로 연결을 받을 준비가 되었는지는 보장하지 않습니다.
healthcheck와 condition 조합
services:
api:
depends_on:
db:
condition: service_healthy # db가 healthy 상태가 될 때까지 대기
redis:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
healthcheck:
컨테이너 설정을 이어서 정의합니다.
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
이렇게 하면 PostgreSQL이 실제로 연결을 받을 준비가 된 후에야 API 서비스가 시작됩니다.
condition 옵션
service_started: 컨테이너가 시작됨 (기본값)service_healthy: 헬스체크가 통과함service_completed_successfully: 컨테이너가 성공적으로 종료됨 (마이그레이션 등)
services:
migrate:
image: myapi:latest
command: npm run migrate
depends_on:
db:
condition: service_healthy
api:
image: myapi:latest
depends_on:
migrate:
condition: service_completed_successfully
db:
condition: service_healthy
환경변수 관리
방법 1: environment 직접 지정
services:
api:
environment:
NODE_ENV: production
DB_HOST: db
DB_PORT: "5432"
방법 2: env_file
services:
api:
env_file:
- .env.common
- .env.api
# .env.api
NODE_ENV=production
DB_HOST=db
DB_PORT=5432
SECRET_KEY=mysecret
방법 3: .env 파일 (Compose 변수 치환)
프로젝트 루트의 .env 파일은 Compose 파일 내 ${VARIABLE} 치환에 사용됩니다.
# .env
POSTGRES_VERSION=16
APP_PORT=8080
services:
db:
image: postgres:${POSTGRES_VERSION}-alpine
api:
ports:
- "${APP_PORT}:8080"
변수 우선순위
docker compose run -e커맨드라인 인자environment에 직접 설정된 값env_file의 값- Dockerfile의
ENV .env파일 (Compose 치환용)
주요 명령어
# 서비스 시작 (빌드 필요 시 자동 빌드)
docker compose up -d
# 서비스 중지 (컨테이너 유지)
docker compose stop
# 서비스 시작 (중지된 컨테이너)
docker compose start
# 서비스 중지 + 컨테이너/네트워크 삭제
docker compose down
# 볼륨까지 삭제
docker compose down -v
# 이미지 재빌드 후 시작
docker compose up -d --build
# 특정 서비스만 실행
docker compose up -d api
실행 명령과 나머지 설정을 이어서 정의합니다.
# 서비스 로그 확인
docker compose logs -f api
# 서비스 상태 확인
docker compose ps
# 서비스 스케일링
docker compose up -d --scale api=3
# 설정 파일 유효성 검사
docker compose config
# 일회성 명령 실행
docker compose run --rm api npm run migrate
# 실행 중인 서비스에 명령 실행
docker compose exec db psql -U postgres
실전 예제: 웹 애플리케이션 스택
# compose.yaml
services:
# Nginx 리버스 프록시
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/nginx/certs:ro
네트워크 관련 설정을 이어서 정의합니다.
depends_on:
api:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
# API 서버
api:
build:
context: ./api
target: production
헬스체크와 의존성 설정을 이어서 정의합니다.
expose:
- "8080"
environment:
NODE_ENV: production
DB_HOST: db
DB_PORT: "5432"
REDIS_URL: redis://redis:6379
env_file:
- .env.api
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 10s
네트워크 관련 설정을 이어서 정의합니다.
timeout: 5s
retries: 3
start_period: 15s
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
- backend
최상위 volumes와 networks 섹션에서 서비스 간 공유 리소스를 정의합니다.
# PostgreSQL
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_FILE: /run/secrets/db_password
헬스체크와 의존성 설정을 이어서 정의합니다.
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- backend
헬스체크와 의존성 설정을 이어서 정의합니다.
# Redis
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 256mb
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
networks:
- backend
볼륨 관련 설정을 이어서 정의합니다.
networks:
frontend:
backend:
volumes:
pg-data:
redis-data:
secrets:
db_password:
file: ./secrets/db_password.txt
주의할 점
1. depends_on만으로는 서비스 준비 완료를 보장할 수 없다
depends_on: [db]는 db 컨테이너가 "시작"된 후 app을 시작한다는 뜻이지, db가 연결을 받을 준비가 되었다는 뜻이 아닙니다. PostgreSQL이나 MySQL은 시작 후 초기화에 수십 초가 걸릴 수 있어서, app이 연결을 시도하면 "Connection refused"가 발생합니다. healthcheck + condition: service_healthy를 반드시 함께 사용하세요.
2. .env 파일의 변수가 시스템 환경변수로 덮어씌워진다
Compose는 .env 파일보다 셸 환경변수를 우선합니다. 셸에 export DB_HOST=production-db가 남아있으면 .env의 DB_HOST=localhost가 무시됩니다. 개발 환경에서 "왜 프로덕션 DB에 연결되지?"라는 사고가 이 원인인 경우가 많습니다. docker compose config로 실제 적용되는 값을 확인하세요.
3. Compose 프로젝트 이름이 같으면 다른 디렉토리의 Compose가 서로 충돌한다
Compose는 디렉토리 이름을 프로젝트 이름으로 사용합니다. 두 프로젝트가 같은 이름의 디렉토리에 있으면 컨테이너, 네트워크, 볼륨이 충돌합니다. -p 플래그나 COMPOSE_PROJECT_NAME 환경변수로 프로젝트 이름을 명시적으로 지정하세요.
정리
- Docker Compose는 여러 컨테이너를 하나의
compose.yaml파일로 정의하고docker compose up하나로 실행합니다. depends_on만으로는 서비스 준비를 보장할 수 없으므로,healthcheck+condition: service_healthy를 함께 사용합니다.- 환경변수는
environment,env_file,.env를 상황에 맞게 조합합니다. 민감한 정보는secrets를 활용하세요. - 네트워크를 분리하여 프론트엔드/백엔드 계층을 격리하면 보안이 강화됩니다.
docker compose config로 최종 설정을 미리 확인하면 실수를 줄일 수 있습니다.