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 컨테이너를 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: secretdocker 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 설정 필요 없다.
5 healthcheck + depends_on condition
depends_on 기본 동작은 “컨테이너가 시작되었는가”만 본다. 하지만 postgres가 시작됐다고 즉시 연결을 받을 준비가 된 것은 아니다 — 초기화 1~10초가 걸린다. api가 그 사이 연결을 시도하면 실패.
해결: postgres에 healthcheck 정의 + api의 depends_on에 condition: 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.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 환경변수로 자동:
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 -dprofiles가 지정된 서비스는 명시 호출 시만 시작. 여러 profile에 속하면 둘 중 하나만 호출되어도 시작.
MINERVA 운영에서 활용 패턴: - 기본: api + redis + 핵심 - 모니터링: + prometheus + grafana - 디버깅: + adminer (DB 관리 UI)
8 환경변수 + .env 통합
Compose는 같은 디렉토리의 .env 파일을 자동 로드한다 (환경변수와 dotenv 참조).
# 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와 다름).env는 Compose 명령 자체의 변수 보간용이고, env_file은 컨테이너 내부에 주입되는 환경변수용이다. 두 역할이 다르다.
| 형태 | 위치 | 역할 |
|---|---|---|
${VAR} |
yml 안 어디든 | Compose가 yml 파싱 시 치환 (.env에서 값 가져옴) |
environment: VAR=... |
service 안 | 컨테이너 환경변수로 주입 |
env_file: [.env.X] |
service 안 | 외부 파일에서 컨테이너 환경변수 일괄 주입 |
.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 자주 발생하는 오류 패턴
CORRECT:
-v 플래그는 named volume을 함께 제거한다. postgres 데이터·ALBERT weight 등이 들어 있는 volume이 사라지면 복구 불가. 의도적 정리 외에는 -v 사용 금지.
CORRECT:
postgres:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
api:
depends_on:
postgres:
condition: service_healthy # healthcheck 통과까지 대기기본 depends_on은 컨테이너 프로세스 시작만 확인한다. DB가 연결을 받을 준비가 됐는지는 모른다. healthcheck + condition으로 진짜 ready 상태 확인.
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을 권장.
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 관련 주제
선행 학습
- Docker 기초 (02) – 단일 컨테이너·Dockerfile 멀티스테이지
- YAML 기초 – compose yml 문법 (anchor·merge key 활용)
- 환경변수와 dotenv – .env 파일 관리
MINERVA 시리즈 응용
- MINERVA 프로덕션 배포 (07-0) – Compose 운영
- MINERVA Config 운영 패턴 (11-1) – Reproducible Build + Compose 통합
- MINERVA CI/CD GitHub Actions (07-1) – deploy 워크플로에서 Compose pull/up