Docker Compose 기초

여러 컨테이너 + 네트워크 + 볼륨을 한 yml로 묶기

단일 docker run으로 시작한 컨테이너 운영이 멀티 서비스(API + DB + 캐시 + frontend)로 커지면 명령어가 폭발한다. Docker Compose가 이를 yml 파일 1개로 묶어 up/down 한 번으로 운영한다. 본 글은 services·networks·volumes 구조, depends_on·healthcheck 의존성, 환경별 override, profiles 선택 실행, .env 통합 패턴을 정리한다. MINERVA 07-0편 운영의 토대.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 06일

1 왜 Compose를 알아야 하는가

Docker 기초 (02)가 단일 컨테이너 운영(docker run, Dockerfile 멀티스테이지, volume 마운트)을 다뤘다. 그러나 실제 운영에서는 여러 서비스가 함께 동작한다 — API 컨테이너 + Postgres + Redis + Frontend 같이.

docker run 명령으로 4개를 띄우면:

# 단일 명령으로는 도저히 관리 불가
docker network create minerva-net
docker run -d --name postgres --network minerva-net -e POSTGRES_PASSWORD=... postgres:15
docker run -d --name redis --network minerva-net redis:7
docker run -d --name api --network minerva-net -p 8000:8000 \
    --env-file .env.production \
    -v $(pwd)/data:/app/data \
    --depends ...                                          # docker run에는 없음
docker run -d --name frontend --network minerva-net -p 3000:3000 ...

문제: - 명령이 길고 외우기 어렵다 - 의존성 순서를 사람이 직접 지키지 않으면 실패 (postgres 안 떠 있는데 api 시작) - 한 서비스 끄려면 docker stop + docker rm - 다른 머신에서 같은 환경 재현하려면 명령을 모두 옮겨 적어야

Docker Compose는 이 4개 컨테이너 + 네트워크 + 의존성을 docker-compose.yml 한 파일에 정의하고 docker compose up 한 명령으로 시작·중지한다. MINERVA 07-0편 프로덕션 배포·11-1편 Config 운영이 직접 의존하는 도구.

2 정의와 구조

정의: Docker Compose

여러 Docker 컨테이너를 yml 파일 1개로 정의·실행·관리하는 도구. Docker Desktop·Docker Engine과 함께 자동 설치되며 docker compose 서브명령으로 사용한다.

  • services: 컨테이너 정의
  • networks: 컨테이너 간 통신 네트워크
  • volumes: 데이터 영속화 볼륨
  • profiles: 선택적 서비스 그룹
# docker-compose.yml — 최소 형태
services:
  api:
    image: minerva:latest
    ports:
      - "8000:8000"

  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
docker compose up -d                      # 백그라운드로 모두 시작
docker compose ps                         # 실행 중인 서비스 확인
docker compose logs api                   # api 로그
docker compose down                       # 모두 중지·제거

기본 동작: - 같은 디렉토리의 docker-compose.yml을 자동 발견 - 모든 서비스가 자동으로 같은 네트워크에 연결됨 (서비스 이름이 DNS host) - up 시 정의된 순서가 아닌 의존성 순서로 시작 (위 예에서는 postgres 먼저)

3 services — 핵심 필드

services:
  api:
    # 1. 이미지 또는 빌드
    image: minerva:latest                       # 기존 이미지 사용
    # 또는
    build:                                       # 로컬 Dockerfile 빌드
      context: .
      dockerfile: Dockerfile
      args:
        COMMIT_SHA: ${GIT_SHA}                   # 환경변수 보간

    # 2. 컨테이너 이름 (선택, 미지정 시 자동 생성)
    container_name: minerva-api

    # 3. 포트 매핑 (host:container)
    ports:
      - "8000:8000"

    # 4. 환경변수
    environment:
      LOG_LEVEL: INFO
      WARMUP_ON_STARTUP: "true"
    env_file:
      - .env.production                         # 외부 파일에서 일괄 로드

    # 5. 볼륨 마운트 (host:container[:options])
    volumes:
      - ./data/configs:/app/data/configs:ro     # 읽기 전용
      - ./data/runtime:/app/data/runtime        # 읽기·쓰기 (기본)
      - minerva-models:/app/data/models:ro      # named volume

    # 6. 의존성 (다른 서비스가 먼저 시작되어야 함)
    depends_on:
      postgres:
        condition: service_healthy              # healthcheck 통과까지 대기
      redis:
        condition: service_started              # 시작만 확인 (기본)

    # 7. 재시작 정책
    restart: unless-stopped                     # 사용자 stop 외에는 자동 재시작

    # 8. 자원 제한
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: "2"

    # 9. healthcheck
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s                          # 시작 후 grace 기간

    # 10. 명령 override (Dockerfile의 CMD 대체)
    command: ["uvicorn", "services.api.main:app", "--host", "0.0.0.0"]

각 필드는 docker run 옵션과 1:1 대응. yml로 옮기면 명령어 외울 필요 없고 git에 추적된다.

4 다중 서비스 — 서비스 간 통신

같은 Compose 파일의 서비스들은 자동으로 같은 네트워크에 들어가고, 서비스 이름 자체가 DNS host가 된다.

services:
  api:
    image: minerva:latest
    environment:
      DATABASE_URL: postgresql://postgres:5432/minerva
      #              ^^^^^^^^^^^^^^^^^^ ↑
      #              서비스 이름 = DNS host (자동 등록)

  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: minerva
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

api 컨테이너 안에서 postgres:5432로 접속하면 postgres 컨테이너의 5432 포트로 연결된다. 별도 네트워크 정의·DNS 설정 필요 없다.

명시적 네트워크가 필요한 경우

여러 Compose 파일의 서비스가 통신해야 하거나 외부 네트워크에 연결할 때:

networks:
  shared:
    external: true                              # 다른 곳에서 만든 네트워크 사용

services:
  api:
    networks:
      - shared
      - default                                  # 자체 네트워크에도

5 healthcheck + depends_on condition

depends_on 기본 동작은 “컨테이너가 시작되었는가”만 본다. 하지만 postgres가 시작됐다고 즉시 연결을 받을 준비가 된 것은 아니다 — 초기화 1~10초가 걸린다. api가 그 사이 연결을 시도하면 실패.

해결: postgres에 healthcheck 정의 + api의 depends_oncondition: service_healthy.

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  api:
    image: minerva:latest
    depends_on:
      postgres:
        condition: service_healthy              # ← postgres healthcheck 통과까지 대기

이 패턴이 MINERVA 07-0편 ALBERT warmup의 lifespan에 자연스럽게 연결된다 — Compose가 의존성 순서를 보장하고, lifespan이 컨테이너 안에서 추가 warmup을 처리한다.

6 환경별 override — 개발 vs 운영

같은 base 정의 위에 환경별 차이만 별도 파일에 둔다.

# docker-compose.yml (base — 공통)
services:
  api:
    image: minerva:latest
    ports:
      - "8000:8000"
# docker-compose.dev.yml (개발 override)
services:
  api:
    build:                                       # 운영은 image, 개발은 build
      context: .
    volumes:
      - ./src:/app/src                           # 소스 코드 hot-reload
    environment:
      LOG_LEVEL: DEBUG
    command: ["uvicorn", "services.api.main:app", "--reload"]   # 개발 reload
# docker-compose.prod.yml (운영 override)
services:
  api:
    image: minervaregistry.azurecr.io/minerva:${GIT_SHA}
    deploy:
      resources:
        limits:
          memory: 4G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      retries: 3

실행:

# 개발
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# 운영
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

또는 COMPOSE_FILE 환경변수로 자동:

export COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
docker compose up -d                             # 위 두 파일 자동 merge

7 profiles — 선택적 서비스

전체 서비스 중 일부만 띄우고 싶을 때 profiles로 그룹핑한다.

services:
  api:
    image: minerva:latest                        # profile 없음 = 항상 시작

  postgres:
    image: postgres:15

  monitoring:
    image: prom/prometheus:latest
    profiles: [monitoring]                      # 명시 호출 시만 시작

  loki:
    image: grafana/loki:latest
    profiles: [monitoring, observability]
docker compose up -d                             # api + postgres만
docker compose --profile monitoring up -d       # api + postgres + prometheus + loki
docker compose --profile monitoring --profile observability up -d

profiles가 지정된 서비스는 명시 호출 시만 시작. 여러 profile에 속하면 둘 중 하나만 호출되어도 시작.

MINERVA 운영에서 활용 패턴: - 기본: api + redis + 핵심 - 모니터링: + prometheus + grafana - 디버깅: + adminer (DB 관리 UI)

8 환경변수 + .env 통합

Compose는 같은 디렉토리의 .env 파일을 자동 로드한다 (환경변수와 dotenv 참조).

# .env (Compose 자동 발견)
GIT_SHA=a3f2b1c
POSTGRES_PASSWORD=secret123
AZURE_OPENAI_API_KEY=sk-abc123
# docker-compose.yml
services:
  api:
    image: minerva:${GIT_SHA}                    # ${VAR} 또는 ${VAR:-default}
    environment:
      AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY}
    env_file:
      - .env.production                          # 컨테이너 내부 환경변수 (.env와 다름)

.envCompose 명령 자체의 변수 보간용이고, env_file컨테이너 내부에 주입되는 환경변수용이다. 두 역할이 다르다.

형태 위치 역할
${VAR} yml 안 어디든 Compose가 yml 파싱 시 치환 (.env에서 값 가져옴)
environment: VAR=... service 안 컨테이너 환경변수로 주입
env_file: [.env.X] service 안 외부 파일에서 컨테이너 환경변수 일괄 주입
.env가 git에 들어가지 않는다

.env 파일은 시크릿이 들어갈 가능성이 크므로 .gitignore에 등록한다. 대신 .env.example을 git에 두고 새 개발자가 cp .env.example .env로 시작.

9 자주 쓰는 명령어

# 시작·중지
docker compose up                                # foreground (Ctrl+C로 중지)
docker compose up -d                             # background (detach)
docker compose down                              # 컨테이너+네트워크 제거
docker compose down -v                           # volume도 제거 (위험 — 데이터 손실)

# 상태 확인
docker compose ps                                # 실행 중인 서비스
docker compose logs                              # 모든 서비스 로그
docker compose logs -f api                       # api 로그 실시간 추적
docker compose logs --tail=100 api               # 최근 100줄

# 한 서비스만 조작
docker compose up -d postgres                    # postgres만 시작
docker compose restart api                       # api만 재시작
docker compose stop api                          # api만 중지

# 컨테이너 안 명령
docker compose exec api bash                     # api 컨테이너 안에 셸
docker compose exec postgres psql -U postgres    # postgres CLI

# 빌드
docker compose build                             # 모든 build: 서비스 다시 빌드
docker compose build api                         # api만
docker compose up --build -d                     # 빌드 + 시작

10 MINERVA 운영 매핑

MINERVA 07-0편 docker-compose 예시의 패턴.

# docker-compose.yml (MINERVA 운영)
services:
  api:
    image: minervaregistry.azurecr.io/minerva:${GIT_SHA}
    container_name: minerva-api
    ports:
      - "8000:8000"
    env_file:
      - .env.cloud                              # Azure 자격증명
    volumes:
      - ./data/configs:/app/data/configs:ro
      - ./data/runtime:/app/data/runtime
      - minerva-models:/app/data/models:ro      # ALBERT weight (volume)
      - minerva-faiss:/app/data/faiss_index:ro
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      retries: 3
      start_period: 60s                          # warmup 동안 grace
    deploy:
      resources:
        limits:
          memory: 4G
    restart: unless-stopped

volumes:
  minerva-models:
    external: true                               # 모델·인덱스는 외부에서 관리
  minerva-faiss:
    external: true

핵심 설계:

  • 이미지 태그가 ${GIT_SHA}로 보간 → 11-1 Reproducible Build의 commit hash baking과 연동
  • ALBERT weight·FAISS 인덱스를 외부 volume으로 분리 → 이미지 경량화 + 모델 교체 시 컨테이너만 재시작
  • start_period: 60s로 ALBERT warmup grace 기간 확보 → 그 사이 healthcheck 실패해도 컨테이너 죽지 않음

11 자주 발생하는 오류 패턴

WRONG:

docker compose down -v                          # -v 플래그 = named volume도 제거

CORRECT:

docker compose down                              # 볼륨은 보존 (기본)

-v 플래그는 named volume을 함께 제거한다. postgres 데이터·ALBERT weight 등이 들어 있는 volume이 사라지면 복구 불가. 의도적 정리 외에는 -v 사용 금지.

WRONG:

api:
  depends_on:
    - postgres                                   # 시작만 확인 (기본)

CORRECT:

postgres:
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]

api:
  depends_on:
    postgres:
      condition: service_healthy                 # healthcheck 통과까지 대기

기본 depends_on은 컨테이너 프로세스 시작만 확인한다. DB가 연결을 받을 준비가 됐는지는 모른다. healthcheck + condition으로 진짜 ready 상태 확인.

WRONG:

volumes:
  - ./data/runtime:/app/data/runtime             # 컨테이너 내부 user와 host user 권한 차이로 쓰기 실패 가능

CORRECT:

# 옵션 1: container 안 user를 host와 일치
user: "${UID:-1000}:${GID:-1000}"

# 옵션 2: named volume 사용 (Docker가 권한 관리)
volumes:
  - minerva-runtime:/app/data/runtime

volumes:
  minerva-runtime:

host에서 마운트한 디렉토리는 host의 file permission이 그대로다. 컨테이너 내부 user(보통 root 또는 1000)와 host user UID가 다르면 쓰기 실패. 운영에서는 named volume을 권장.

WRONG:

services:
  api:
    environment:
      AZURE_OPENAI_API_KEY: "sk-abc123..."       # yml에 hardcode

CORRECT:

services:
  api:
    env_file:
      - .env.production                           # .gitignore에 등록된 외부 파일
    # 또는
    environment:
      AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY}   # .env에서 보간

yml에 시크릿 hardcode 금지. .env.production.gitignore에 두고 env_file로 주입.

12 정리

영역 핵심
services image/build, ports, environment, volumes, depends_on, healthcheck
네트워킹 같은 compose 파일은 자동 네트워크 + 서비스 이름 = DNS
depends_on 기본은 시작만 확인. condition: service_healthy로 ready까지
환경별 docker-compose.yml + docker-compose.dev.yml/prod.yml merge
profiles 선택적 서비스 그룹 (--profile monitoring)
환경변수 .env (Compose 보간) + env_file (컨테이너 내부) 분리
명령 up/down/ps/logs/exec/build/restart

13 응용 분야

MINERVA 운영 매핑 본 글 절
07-0 docker-compose 예시 services + volumes + healthcheck + start_period
07-1 deploy 워크플로 image: ${GIT_SHA} 보간 + Compose pull/up
11-1 시크릿 주입 env_file: .env.cloud + Key Vault 연동
ALBERT volume 분리 external named volume

14 관련 주제

선행 학습

MINERVA 시리즈 응용

Subscribe

Enjoy this blog? Get notified of new posts by email: