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
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
# 또는 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
# 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
정리
- Docker Compose는 여러 컨테이너를 하나의
compose.yaml파일로 정의하고docker compose up하나로 실행합니다. depends_on만으로는 서비스 준비를 보장할 수 없으므로,healthcheck+condition: service_healthy를 함께 사용합니다.- 환경변수는
environment,env_file,.env를 상황에 맞게 조합합니다. 민감한 정보는secrets를 활용하세요. - 네트워크를 분리하여 프론트엔드/백엔드 계층을 격리하면 보안이 강화됩니다.
docker compose config로 최종 설정을 미리 확인하면 실수를 줄일 수 있습니다.
댓글 로딩 중...