Docker Compose 기초 — env_file, 변수 치환, depends_on

env_file과 환경변수 치환(interpolation)의 차이, depends_on + healthcheck로 서비스 시작 순서를 제어하는 방법을 실제 에러 케이스와 함께 설명한다.

Seobway · · 11분

가장 흔한 Compose 에러

invalid interpolated value: ports: ${WEB_PORT}: mandatory variable 'WEB_PORT' is not set

이 에러를 처음 보면 당황스럽다.
.env.localWEB_PORT=8000이 분명히 있는데 왜 못 읽을까?

원인은 env_file변수 치환(interpolation)의 차이를 몰랐기 때문이다.

Docker Compose 기본 구조

# docker-compose.yml
version: "3.9"

services:        # 실행할 서비스(컨테이너) 정의
  web:
    build: .     # Dockerfile로 이미지 빌드
    ports:
      - "${WEB_PORT}:${WEB_PORT}"
    env_file:
      - .env.local
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  mysql_data:

env_file vs 변수 치환 — 핵심 차이

%% desc: env_file은 컨테이너 내부에만 주입, 변수 치환은 Compose 실행 시점에 .env 파일에서 읽음
flowchart TD
  subgraph COMPOSE_TIME["Compose 실행 시점 (docker compose up)"]
    DOTENV[".env 파일\nWEB_PORT=8000"]
    SHELL["쉘 환경변수\nexport WEB_PORT=8000"]
    INTERP["Compose 변수 치환\n${WEB_PORT} → 8000\nposts: - '8000:8000'"]
    DOTENV & SHELL --> INTERP
  end

  subgraph CONTAINER_TIME["컨테이너 실행 시점"]
    ENVFILE[".env.local\nWEB_PORT=8000\nDJANGO_SECRET_KEY=..."]
    CONTAINER["컨테이너 내부\n환경변수로 주입"]
    ENVFILE --> CONTAINER
  end

  COMPOSE_TIME --> CONTAINER_TIME
env_file: 변수 치환 (${VAR})
읽는 시점 컨테이너 시작 시 docker compose up 명령 실행 시
적용 범위 컨테이너 내부 환경변수 docker-compose.yml 파일의 ${VAR}
읽는 파일 env_file:에 명시한 파일 프로젝트 루트의 .env 또는 쉘 환경변수
사용 예 DJANGO_SECRET_KEY, DATABASE_URL ports, image 태그, container_name

해결책

# 프로젝트 루트에 Compose 치환 전용 .env 파일 생성
# (컨테이너 내부 값이 아닌, Compose 파일 렌더링용)
cat > .env << EOF
WEB_PORT=8000
COMPOSE_PROJECT_NAME=myproject
EOF

.envdocker compose 명령 실행 시 자동으로 읽힌다.[1]
별도 설정 없이 ${WEB_PORT}8000으로 치환된다.

.
├── .env              ← Compose 변수 치환용 (WEB_PORT 등)
├── .env.local        ← 컨테이너 내부 환경변수용 (env_file:)
└── docker-compose.yml

.env는 보통 .gitignore에 포함되지 않는다(값이 단순한 포트 번호 등이므로).
반면 .env.local은 시크릿 키, DB 비밀번호를 포함하므로 반드시 .gitignore에 추가.

depends_on + healthcheck — 시작 순서 제어

서비스 간 의존성이 있을 때 사용한다.
예: Django는 MySQL이 준비된 후 시작해야 한다.

depends_on만 쓰면 부족하다

# 이것만으로는 부족하다
depends_on:
  - db

depends_on만 사용하면 컨테이너가 시작된 시점을 기다릴 뿐,
MySQL이 실제로 연결을 받을 준비가 됐는지는 보장하지 않는다.

healthcheck + condition 조합

services:
  web:
    depends_on:
      db:
        condition: service_healthy   # DB가 healthy 상태일 때만 web 시작
      redis:
        condition: service_healthy

  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s      # 5초마다 체크
      timeout: 3s       # 3초 내 응답 없으면 실패
      retries: 5        # 5번 실패하면 unhealthy
      start_period: 10s # 처음 10초는 실패해도 카운트 안 함

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
%% desc: depends_on + healthcheck 동작 흐름 — MySQL healthy 확인 후 Django 시작
sequenceDiagram
  participant DC as Docker Compose
  participant MYSQL as MySQL Container
  participant REDIS as Redis Container
  participant WEB as Django Container

  DC->>MYSQL: 컨테이너 시작
  DC->>REDIS: 컨테이너 시작
  DC->>DC: web 시작 대기 (condition: service_healthy)

  loop healthcheck (5초마다)
    DC->>MYSQL: mysqladmin ping
    MYSQL-->>DC: 응답 없음 (초기화 중)
  end

  MYSQL-->>DC: pong ✅ (healthy)
  REDIS-->>DC: pong ✅ (healthy)

  DC->>WEB: 컨테이너 시작 (둘 다 healthy 확인 후)

자주 쓰는 Compose 키워드

services:
  web:
    # 이미지 빌드
    build:
      context: .
      dockerfile: Dockerfile

    # 또는 기존 이미지 사용
    image: python:3.12-slim

    # 컨테이너 이름 명시
    container_name: pms_v3-web

    # 항상 재시작
    restart: unless-stopped

    # 볼륨 마운트
    volumes:
      - .:/app                        # Bind Mount (코드 동기화)
      - static_files:/app/staticfiles  # Named Volume

    # 환경변수 직접 정의
    environment:
      - DEBUG=true
      - PYTHONUNBUFFERED=1

    # 환경변수 파일 지정 (컨테이너 내부용)
    env_file:
      - .env.local

    # 포트 매핑 (호스트:컨테이너)
    ports:
      - "${WEB_PORT}:8000"

    # 실행 명령 오버라이드
    command: python manage.py runserver 0.0.0.0:8000

    # 네트워크 명시 (기본은 자동 생성)
    networks:
      - backend_network

유용한 Compose 명령어

# 서비스 시작 (백그라운드)
docker compose up -d

# 특정 서비스만 시작
docker compose up -d web

# 이미지 강제 재빌드
docker compose up -d --build

# 로그 확인
docker compose logs -f web

# 컨테이너 내 명령 실행
docker compose exec web python manage.py migrate

# 서비스 상태 확인
docker compose ps

# 중지 (컨테이너, 네트워크 삭제 / 볼륨은 유지)
docker compose down

# 중지 + 볼륨까지 삭제 (DB 초기화)
docker compose down -v

참고

  1. Docker Inc., Set environment variables in Compose — Docker Docs

관련 글