Theme:

개발할 때는 소스 코드를 마운트하고 핫 리로드를 쓰지만, 프로덕션에서는 이미지를 빌드해서 배포합니다. 같은 Compose 파일로 두 환경을 어떻게 관리할 수 있을까요?

profiles — 서비스 선택적 활성화

모든 서비스가 항상 필요한 것은 아닙니다. 디버깅 도구, 모니터링, 데이터 마이그레이션 등은 특정 상황에서만 실행하고 싶을 때 profiles를 사용합니다.

YAML
# compose.yaml
services:
  api:
    build: ./api
    ports:
      - "8080:8080"

  db:
    image: postgres:16-alpine
    volumes:
      - pg-data:/var/lib/postgresql/data

  # 디버깅 프로파일
  adminer:
    image: adminer
    ports:
      - "8888:8080"
    profiles:
      - debug

  # 모니터링 프로파일
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    profiles:
      - monitoring

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    profiles:
      - monitoring

  # 데이터 시딩
  seed:
    build: ./api
    command: npm run seed
    depends_on:
      db:
        condition: service_healthy
    profiles:
      - seed

volumes:
  pg-data:
BASH
# 기본 서비스만 시작 (api, db)
docker compose up -d

# 디버깅 도구 포함
docker compose --profile debug up -d

# 모니터링 포함
docker compose --profile monitoring up -d

# 여러 프로파일 동시
docker compose --profile debug --profile monitoring up -d

# 시드 데이터 실행 (한 번 실행 후 종료)
docker compose run --rm --profile seed seed

profiles가 없는 서비스는 항상 시작되고, profiles가 있는 서비스는 해당 프로파일이 활성화될 때만 시작됩니다.

override와 다중 Compose 파일

compose.override.yaml

docker compose up을 실행하면 compose.yamlcompose.override.yaml자동으로 병합됩니다.

YAML
# compose.yaml — 공통 설정
services:
  api:
    build: ./api
    environment:
      NODE_ENV: production
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  pg-data:
YAML
# compose.override.yaml — 개발 환경 (자동 병합)
services:
  api:
    build:
      context: ./api
      target: development   # 개발용 빌드 타겟
    environment:
      NODE_ENV: development
      DEBUG: "true"
    ports:
      - "8080:8080"
      - "9229:9229"        # 디버거 포트
    volumes:
      - ./api/src:/app/src  # 소스 코드 마운트
    restart: "no"

  db:
    ports:
      - "5432:5432"         # 로컬에서 직접 접근 가능
    environment:
      POSTGRES_PASSWORD: devpassword
BASH
# 개발 환경 (compose.yaml + compose.override.yaml 자동 병합)
docker compose up -d

# 프로덕션 — override 무시하고 prod 파일 사용
docker compose -f compose.yaml -f compose.prod.yaml up -d

프로덕션 파일

YAML
# compose.prod.yaml
services:
  api:
    image: myregistry.com/myapi:${TAG:-latest}
    # build 대신 이미지 사용
    environment:
      NODE_ENV: production
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1G
      replicas: 2

  db:
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

병합 규칙

  • 단일 값 (image, command 등): 나중 파일이 덮어씀
  • 리스트 (ports, volumes 등): 합쳐짐 (append)
  • (environment 등): 키 단위로 병합, 같은 키는 덮어씀

extends — 서비스 설정 상속

YAML
# common-services.yaml
services:
  base-api:
    build:
      context: .
    environment:
      LOG_LEVEL: info
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3
    restart: unless-stopped
YAML
# compose.yaml
services:
  user-api:
    extends:
      file: common-services.yaml
      service: base-api
    build:
      context: ./user-service
    ports:
      - "8081:8080"
    environment:
      SERVICE_NAME: user-api
      # LOG_LEVEL: info는 상속됨

  order-api:
    extends:
      file: common-services.yaml
      service: base-api
    build:
      context: ./order-service
    ports:
      - "8082:8080"
    environment:
      SERVICE_NAME: order-api

watch 모드 — 파일 변경 자동 감지

Docker Compose V2.22+에서 지원하는 기능으로, 파일 변경을 감지하여 자동으로 반영합니다.

YAML
# compose.yaml
services:
  api:
    build: ./api
    ports:
      - "8080:8080"
    develop:
      watch:
        # 소스 코드 변경 → 컨테이너에 동기화
        - action: sync
          path: ./api/src
          target: /app/src
          ignore:
            - "**/*.test.ts"

        # package.json 변경 → 이미지 재빌드
        - action: rebuild
          path: ./api/package.json

        # 설정 파일 변경 → 동기화 후 재시작
        - action: sync+restart
          path: ./api/config
          target: /app/config
BASH
# watch 모드 시작
docker compose watch

# 또는 up과 함께
docker compose up --watch

watch 액션 종류

액션동작사용 사례
sync파일을 컨테이너에 동기화핫 리로드 지원 소스 코드
rebuild이미지를 다시 빌드하고 서비스 재시작의존성 변경 (package.json)
sync+restart파일 동기화 후 서비스 재시작설정 파일 변경

개발 환경 핫 리로드 구성

Node.js (nodemon)

YAML
services:
  api:
    build:
      context: ./api
      target: development
    command: npx nodemon --watch src src/server.ts
    volumes:
      - ./api/src:/app/src
      - ./api/package.json:/app/package.json
      - api-node-modules:/app/node_modules
    ports:
      - "8080:8080"
      - "9229:9229"
    environment:
      NODE_ENV: development

volumes:
  api-node-modules:

React (Vite)

YAML
services:
  frontend:
    build:
      context: ./frontend
      target: development
    command: npm run dev -- --host 0.0.0.0
    volumes:
      - ./frontend/src:/app/src
      - ./frontend/public:/app/public
      - frontend-node-modules:/app/node_modules
    ports:
      - "5173:5173"
    environment:
      VITE_API_URL: http://localhost:8080

volumes:
  frontend-node-modules:

Spring Boot (devtools)

YAML
services:
  api:
    build:
      context: ./api
      target: development
    volumes:
      - ./api/src:/app/src
      - gradle-cache:/root/.gradle
    ports:
      - "8080:8080"
      - "5005:5005"
    environment:
      SPRING_PROFILES_ACTIVE: dev
      JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"

volumes:
  gradle-cache:

프로덕션 대비 체크리스트

YAML
# compose.prod.yaml
services:
  api:
    # 1. build 대신 이미지 사용
    image: myregistry.com/myapi:${TAG}

    # 2. 리소스 제한
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1G
        reservations:
          cpus: "0.25"
          memory: 256M

    # 3. 로그 드라이버 설정
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

    # 4. 재시작 정책
    restart: unless-stopped

    # 5. 읽기 전용 파일 시스템
    read_only: true
    tmpfs:
      - /tmp

    # 6. 보안 설정
    security_opt:
      - no-new-privileges:true

    # 7. 헬스체크
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

개발 vs 프로덕션 차이 요약

항목개발프로덕션
이미지 소스build:image:
볼륨소스 코드 바인드 마운트데이터 볼륨만
포트디버거, DB 포트 노출최소한의 포트
리소스 제한없음CPU/메모리 제한
재시작nounless-stopped
로그콘솔 출력로그 드라이버 설정
환경변수.env 파일Secrets

유용한 팁

프로젝트 이름 지정

BASH
# 기본: 디렉토리 이름이 프로젝트 이름
docker compose up -d

# 프로젝트 이름 지정
docker compose -p myproject up -d

# 또는 .env에서
# COMPOSE_PROJECT_NAME=myproject

이미지 빌드와 관리

BASH
# 빌드만 (실행하지 않음)
docker compose build

# 캐시 없이 빌드
docker compose build --no-cache

# 특정 서비스만 빌드
docker compose build api

# 빌드 후 push
docker compose build && docker compose push

로그 관리

BASH
# 전체 서비스 로그
docker compose logs -f

# 특정 서비스, 최근 100줄
docker compose logs -f --tail 100 api

# 타임스탬프 포함
docker compose logs -f -t api

정리

  • profiles로 디버깅, 모니터링 등 선택적 서비스를 관리합니다.
  • compose.override.yaml은 자동 병합되어 개발 환경 설정을 편리하게 추가합니다.
  • extends로 공통 서비스 설정을 재사용하여 중복을 줄입니다.
  • docker compose watch로 파일 변경 시 자동 동기화/재빌드를 구성합니다.
  • 개발과 프로덕션 환경은 기본 파일 + 환경별 파일로 분리하여 관리하는 것이 가장 깔끔합니다.
댓글 로딩 중...