Theme:

웹 서버, API 서버, 데이터베이스, 캐시를 각각 docker run으로 실행하고 네트워크와 볼륨을 수동으로 연결하면 명령어가 너무 길어집니다. 이걸 하나의 파일로 정의할 수는 없을까요?

Docker Compose란

Docker Compose는 여러 컨테이너로 구성된 애플리케이션을 하나의 YAML 파일로 정의하고 관리하는 도구입니다.

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:
BASH
# 모든 서비스 시작
docker compose up -d

# 모든 서비스 중지 및 컨테이너 삭제
docker compose down

# 볼륨까지 삭제
docker compose down -v

compose.yaml 핵심 문법

services

서비스는 하나의 컨테이너를 정의합니다.

YAML
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

YAML
services:
  web:
    networks:
      - frontend

  api:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend

networks:
  frontend:
  backend:

네트워크를 명시하지 않으면 프로젝트명_default 네트워크가 자동 생성되어 모든 서비스가 연결됩니다.

volumes

YAML
services:
  db:
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  pg-data:          # 기본 local 드라이버
  # 외부 볼륨 사용
  shared-data:
    external: true  # docker volume create로 미리 생성된 볼륨

depends_on과 서비스 시작 순서

기본 사용법

YAML
services:
  api:
    depends_on:
      - db
      - redis
    # db와 redis 컨테이너가 "시작된 후"에 api가 시작됨

  db:
    image: postgres:16-alpine

  redis:
    image: redis:7-alpine

하지만 depends_on컨테이너의 시작만 보장합니다. 데이터베이스가 실제로 연결을 받을 준비가 되었는지는 보장하지 않습니다.

healthcheck와 condition 조합

YAML
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: 컨테이너가 성공적으로 종료됨 (마이그레이션 등)
YAML
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 직접 지정

YAML
services:
  api:
    environment:
      NODE_ENV: production
      DB_HOST: db
      DB_PORT: "5432"

방법 2: env_file

YAML
services:
  api:
    env_file:
      - .env.common
      - .env.api
BASH
# .env.api
NODE_ENV=production
DB_HOST=db
DB_PORT=5432
SECRET_KEY=mysecret

방법 3: .env 파일 (Compose 변수 치환)

프로젝트 루트의 .env 파일은 Compose 파일 내 ${VARIABLE} 치환에 사용됩니다.

BASH
# .env
POSTGRES_VERSION=16
APP_PORT=8080
YAML
services:
  db:
    image: postgres:${POSTGRES_VERSION}-alpine
  api:
    ports:
      - "${APP_PORT}:8080"

변수 우선순위

  1. docker compose run -e 커맨드라인 인자
  2. environment에 직접 설정된 값
  3. env_file의 값
  4. Dockerfile의 ENV
  5. .env 파일 (Compose 치환용)

주요 명령어

BASH
# 서비스 시작 (빌드 필요 시 자동 빌드)
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

실전 예제: 웹 애플리케이션 스택

YAML
# 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로 최종 설정을 미리 확인하면 실수를 줄일 수 있습니다.
댓글 로딩 중...