본문으로 건너뛰기
yutils

Docker Compose 완벽 정리 — 서비스·네트워크·볼륨·실전 구성법

docker-compose.yml 의 구조, 각 top-level 키 (services / networks / volumes / configs / secrets) 역할, depends_on 과 healthcheck 차이, named volume vs bind mount, 개발 환경을 깨뜨리는 함정들.

약 10분 읽기

Docker Compose 는 컨테이너 여러 개를 한 번에 정의·실행하는 도구. 로컬 개발 환경의 사실상 표준이고, 작은 production 배포에도 종종 쓰인다. 이 가이드는 docker-compose.yml 의 구조, top-level 키 5 종의 역할, depends_on 과 healthcheck 의 차이, named volume vs bind mount, 그리고 개발 환경을 깨뜨리는 함정을 정리한다.

docker-compose.yml 의 5 가지 최상위 키

# v3+ 형식 — version 키는 더 이상 권장 X
services:        # 1. 컨테이너 정의 (필수)
networks:        # 2. 네트워크 정의
volumes:         # 3. named volume 정의
configs:         # 4. Swarm 설정 (선택)
secrets:         # 5. Swarm 시크릿 (선택)

모든 매니페스트는 services 가 핵심. 다른 4 개는 services 가 참조할 때만 의미. 한 파일에 같이 박힘.

services — 컨테이너 정의

services:
  web:
    image: nginx:1.27         # 이미지 또는 build
    container_name: my-web    # 컨테이너 이름 (선택)
    ports:
      - "80:80"               # host:container
      - "443:443"
    environment:              # env 변수
      LOG_LEVEL: info
      DATABASE_URL: postgres://db/app
    env_file:                 # .env 파일에서 가져옴
      - .env
    depends_on:               # 시작 순서
      - db
    networks:                 # 어떤 네트워크에 연결
      - frontend
    volumes:                  # 어떤 디스크 마운트
      - ./static:/usr/share/nginx/html
      - logs:/var/log/nginx
    restart: unless-stopped   # 재시작 정책
    healthcheck:              # 헬스 체크
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 3s
      retries: 3

서비스 이름 (위 예의 web) 이 컨테이너의 DNS 이름이 됨 — 같은 Compose 안 다른 서비스가 http://web 로 호출 가능. 별도 DNS 설정 0.

image vs build

  • image: Docker Hub 또는 private registry 의 이미지를 pull. image: postgres:16.
  • build: 로컬 Dockerfile 에서 빌드. build: ./api 또는 build: {context: ., dockerfile: Dockerfile.dev}.

둘 다 박으면 build 가 우선 — 빌드된 이미지를 image 이름으로 tag. 일반적 으로 둘 중 하나만.

networks — 서비스 간 격리

networks:
  frontend:
    driver: bridge           # 기본값
  backend:
    driver: bridge
    internal: true           # 외부 차단 (내부 통신만)
  monitoring:
    external: true           # 이미 존재하는 외부 네트워크

같은 네트워크에 속한 서비스만 서로 보임. 위 예: backend 에 박힌 db 는 frontend 의 web 에서 직접 호출 불가. DMZ 패턴 자연 구현.

Compose 가 자동으로 default network (이름: <project>_default) 생성. networks: 안 쓰면 모든 서비스가 자동으로 같은 네트워크에 묶임.

volumes — 영속 데이터

Named volume (권장)

services:
  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data  # named: 마운트

volumes:
  db-data:                                 # top-level 선언
  • Docker 가 관리. 위치는 /var/lib/docker/volumes/<project>_db-data/.
  • docker compose down 으로 컨테이너 삭제해도 볼륨은 남음.
  • docker compose down -v 로 명시적으로 삭제.
  • 백업·migration 친화. 환경 이식성 ↑.

Bind mount (host 파일시스템 직접 마운트)

volumes:
  - ./src:/app/src                 # host ./src → container /app/src
  - /etc/timezone:/etc/timezone:ro # read-only
  • 호스트 OS 의 특정 경로를 마운트.
  • 코드 hot reload (dev 환경) 에 필수.
  • OS 의존성·권한 충돌 위험. production 비추.

tmpfs (메모리)

services:
  api:
    tmpfs:
      - /tmp                  # RAM 마운트 (재시작 시 사라짐)

구조 확인 — Docker Compose 시각화 에 매니페스트 붙여넣으면 named volume 이 어떤 서비스에 연결됐는지 한눈에. bind mount 는 그래프에 안 나타남 (host 의존이라 Compose 가 관리하는 리소스 아님).

depends_on — 시작 순서 ≠ 준비 상태

services:
  api:
    depends_on:
      - db
  db:
    image: postgres:16

Compose 가 db 먼저 시작 → 그 다음 api. 하지만 db 가 받을 준비 가 됐는지는 보장 안 됨. 컨테이너 시작 ≠ 프로세스 ready.

해결: long form + condition 으로 healthcheck 대기.

services:
  api:
    depends_on:
      db:
        condition: service_healthy   # db 의 healthcheck 통과까지 대기
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

condition 값:

  • service_started (기본) — 컨테이너 시작만
  • service_healthy — healthcheck 통과까지
  • service_completed_successfully — 종료까지 (Job 패턴)

environment vs env_file vs secrets

environment

environment:
  - LOG_LEVEL=info             # array 형식
  - DATABASE_URL=postgres://...

# 또는 map 형식
environment:
  LOG_LEVEL: info
  DATABASE_URL: postgres://...

매니페스트에 평문. 시크릿 절대 박지 말 것 — git 에 commit 되면 노출.

env_file

env_file:
  - .env
  - .env.local

.env 파일에서 변수 로드. .env 는 gitignore 대상. dev 환경 표준.

secrets (Swarm)

services:
  db:
    secrets:
      - db-password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db-password

secrets:
  db-password:
    file: ./secrets/db-password.txt

secret 파일이 /run/secrets/<name> 으로 마운트. 환경 변수보다 안전 — 프로세스 환경에 노출 X, docker inspect 결과에도 안 보임.

ports — host : container

ports:
  - "8080:80"             # host 8080 → container 80
  - "127.0.0.1:8080:80"   # localhost 바인딩 (외부 차단)
  - "8080:80/udp"         # UDP
  - "8000-8010:8000-8010" # 범위

ports = 외부 노출. 같은 Compose 안 서비스끼리는 ports 없 어도 서로 통신 가능 (default network 의 DNS 로). ports 는 호스트와 외부 클라이언트용.

실전 — 풀스택 dev 환경

services:
  web:
    build: ./web
    ports: ["3000:3000"]
    volumes:
      - ./web/src:/app/src       # hot reload
    depends_on:
      api:
        condition: service_started
    environment:
      NEXT_PUBLIC_API_URL: http://localhost:8000

  api:
    build: ./api
    ports: ["8000:8000"]
    volumes:
      - ./api:/app
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app

  db:
    image: postgres:16
    ports: ["5432:5432"]         # 로컬 DBeaver 접속용
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports: ["6379:6379"]

volumes:
  db-data:

실제로 매니페스트가 어떻게 연결되는지는 Docker Compose 시각화 에 위 YAML 을 넣으면 그래프로 즉시 보임.

주요 명령

docker compose up -d            # 백그라운드 시작
docker compose up --build       # 이미지 다시 빌드 후 시작
docker compose down             # 컨테이너 + 네트워크 삭제
docker compose down -v          # + 볼륨도 삭제 (주의)
docker compose ps               # 상태 확인
docker compose logs -f api      # 특정 서비스 로그
docker compose exec api bash    # 컨테이너 안 셸
docker compose restart api      # 단일 서비스 재시작
docker compose pull             # 이미지 갱신

Compose vs Kubernetes

Docker ComposeKubernetes
대상단일 호스트, 로컬 개발클러스터, production
학습 곡선가파름 X (수 시간)가파름 (수 주)
HA / 스케일링제한적 (Swarm 모드)1급 (Deployment / HPA)
매니페스트1 파일 (compose.yml)여러 파일 (Helm / Kustomize)

Compose → K8s 마이그레이션 시 kompose convert 같은 도구가 매핑 자동화. Kubernetes YAML 시각화 Docker Compose 시각화 둘 다 같은 시각화 패턴이라 구조 비교 직관적.

흔히 빠지는 함정

1. depends_on 만으로 ready 가정

API 가 시작 즉시 DB connect 시도 → DB 가 listener 띄우는 중 → connection refused. healthcheck + condition: service_healthy 필수.

2. environment 에 시크릿

매니페스트 commit 시 시크릿 노출. env_file + .env (gitignore) 또는 secrets 사용.

3. bind mount 의 권한

호스트의 디렉토리 권한이 컨테이너 안 user 와 안 맞아 write 실패. macOS/ Windows 의 Docker Desktop 은 자동 해결하지만 Linux 는 직접 처리 필요 (user: "${UID}:{GID}").

4. version: "3" 박기

Compose v2+ (CLI 도구 자체) 부터 version: 키 무시. 경고 나옴. 새 매니페스트에서는 빼는 것이 표준.

5. 같은 host port 두 서비스

ports: ["8080:80"] 가 두 서비스에 있으면 두 번째 가 실패. host port 충돌. 한 서비스만 외부 노출 또는 다른 port.

6. networks 안 쓰면 모든 서비스가 같은 망

보안 격리 0. production 또는 보안 민감 환경에서는 명시적 frontend / backend 분리.

7. docker compose down -v 실수

named volume 까지 삭제. DB 데이터 날아감. dev 에서도 가끔 사고 발생. backup 정책 권장.

8. v1 / v2 명령 혼동

docker-compose (하이픈, Python) vs 새 docker compose (공백, Go 플러그인). 새 환경은 무조건 후자. 옛 명령은 2024 년 deprecated.

요약

  • services / networks / volumes / configs / secrets — 5 가지 top-level 키. services 가 핵심.
  • 서비스 이름 = 컨테이너 DNS 이름. 같은 네트워크 안 자동 통신.
  • depends_on 은 시작 순서만. ready 보장 = healthcheck + condition: service_healthy.
  • named volume = Docker 관리, bind mount = host 의존. production 은 named.
  • 시크릿은 environment X, env_file (.env gitignore) 또는 secrets.
  • 구조 확인은 Docker Compose 시각화 — 5 분 안에 그래프 + 의존성 파악.
가이드 목록으로