LabHub
배우기 러닝패스 코스

Volumes, Networks and Compose

What It Means to Declare a Stack in One File

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

Compose 파일은 여러 컨테이너의 실행 인자를 선언형으로 적어 둔 것입니다. 마법이 아니라, docker run 플래그와 일대일로 대응합니다.

Layer map: 여러 컨테이너의 실행 인자를 선언형으로 적어 둔 것 · 어디에도 적혀 있지 않기 때문 · 둘째, dependson 은 순서만 보장하지 준비 상태를 보장하지 않습니다. · 네트워크를 두 개로 나누는 것

왜 이게 필요했나

컨테이너 셋을 손으로 띄우려면 네트워크를 만들고, 볼륨을 만들고, 각각에 --network, -v, -e, -p 를 정확한 순서로 붙여야 합니다. 한 사람이 한 번 하는 데는 문제가 없지만, 두 번째 사람이 같은 스택을 재현하려는 순간부터 지옥이 됩니다. 어디에도 적혀 있지 않기 때문입니다.

Compose 가 실제로 해결하는 문제가 이것입니다. 스택의 모양이 파일 하나에 적혀 있고, 그 파일이 저장소에 커밋됩니다.

어떻게 동작하나

핵심 대응 관계는 이렇습니다.

Compose 키 docker run 대응
image 이미지 인자
command 이미지 뒤의 명령
ports -p
volumes -v / --mount
environment -e
networks --network
depends_on (대응 없음 — 기동 순서만)

여기서 두 가지를 짚어야 합니다.

첫째, Compose 는 프로젝트마다 사용자 정의 네트워크를 자동으로 만들고 서비스 이름을 별칭으로 등록합니다. 그래서 "Compose 에서는 잘 되던 것이 docker run 으로 옮기면 안 되는" 일이 생깁니다. 원인은 Compose 의 마법이 아니라, 손으로 옮길 때 네트워크를 안 만들었기 때문입니다.

둘째, depends_on 은 순서만 보장하지 준비 상태를 보장하지 않습니다. DB 컨테이너가 "시작"된 것과 DB 가 "접속을 받을 준비가 된" 것은 다릅니다. 그래서 헬스체크 조건을 함께 걸거나, 애플리케이션이 재시도하도록 만들어야 합니다. 이 구분을 놓치면 배포할 때마다 첫 몇 초 동안 커넥션 오류가 쏟아집니다.

현장에서 만나는 모습

실전 구성에서 자주 쓰는 패턴은 네트워크를 두 개로 나누는 것입니다. 프론트엔드 네트워크에는 리버스 프록시만, 백엔드 네트워크에는 DB 와 캐시를 두고 앱만 양쪽에 소속시킵니다. 그러면 DB 는 프록시 쪽에서 이름조차 해석되지 않습니다.

그리고 exposeports 와 헷갈리면 안 됩니다. expose 는 호스트에 포트를 열지 않습니다. 문서화 용도입니다. 컨테이너끼리만 통신하는 서비스는 아예 ports 를 쓰지 않는 것이 맞습니다.

마지막으로, 선언과 실제가 어긋나는 순간이 사고의 시작입니다. 누군가 손으로 컨테이너를 고치면 파일과 현실이 갈라지고, 그 갈라짐은 다음 배포 때까지 아무도 모릅니다. 선언과 실제를 대조하는 습관이 그래서 중요합니다.

의존 순서를 제대로 거는 법

depends_on 만으로는 부족합니다. 컨테이너가 시작됐다는 뜻이지 서비스가 준비 됐다는 뜻이 아닙니다. DB 컨테이너는 떴는데 PostgreSQL 이 아직 초기화 중이면 애플리케이션이 연결 실패로 죽습니다.

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s          # 이 동안의 실패는 세지 않는다
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy    # ← 건강해질 때까지 기다린다

start_period 가 중요합니다. 이것이 없으면 초기화 중의 실패가 재시도 횟수를 까먹어, 느린 서비스가 영영 unhealthy 로 판정됩니다.

그래도 애플리케이션은 재연결을 할 줄 알아야 합니다. 운영에서는 compose 가 없고, DB 가 재시작하는 일도 있습니다. 헬스체크는 개발 편의이지 재연결 로직의 대체물이 아닙니다.

환경별로 파일을 겹치기

compose 파일은 여러 개를 겹쳐 쓸 수 있습니다.

docker compose -f compose.yaml -f compose.dev.yaml up

뒤 파일이 앞을 덮습니다. 공통은 compose.yaml 에, 개발용 마운트와 디버그 포트는 compose.dev.yaml 에 둡니다. compose.override.yaml이름만으로 자동 적용 되므로 개발자 개인 설정에 씁니다(.gitignore 에 넣습니다).

# compose.dev.yaml — 소스를 마운트해 즉시 반영
services:
  app:
    volumes: ["./src:/app/src"]
    environment: {DEBUG: "1"}
    command: ["python", "-m", "uvicorn", "app:app", "--reload"]

compose 와 쿠버네티스의 경계

compose 로 잘 도는 것이 쿠버네티스에서 그대로 되지는 않습니다. 옮길 때 걸리는 것들입니다.

compose 쿠버네티스
depends_on 없다 — initContainer 나 재시도로
서비스 이름으로 DNS 같다(Service 이름)
volumes: ./src:/app hostPath — 운영에서는 피한다
restart: always 기본 동작(restartPolicy)
ports: 8080:80 Service + Ingress

kompose 같은 변환 도구가 있지만 결과를 그대로 쓰면 안 됩니다. 자원 요청·한도, 프로브, 보안 컨텍스트가 전부 비어 있기 때문입니다. 변환은 출발점이고 손으로 채워야 합니다.

다음 실습에서 할 것

두 서비스로 이루어진 스택을 Compose 파일로 선언하고, 같은 스택을 손으로도 띄운 뒤, 선언한 값과 실제로 도는 값이 일치하는지 직접 대조합니다.