Docker Compose YAML, 서비스 묶음을 운영 가능한 구성으로 만드는 법
Docker Compose YAML, 서비스 묶음을 운영 가능한 구성으로 만드는 법
compose.yaml은 컨테이너를 나열하는 메모가 아니다. 한 애플리케이션을 이루는 서비스, 통신 경계, 지속 데이터, 실행 순서를 사람이 읽고 도구가 재현할 수 있게 만드는 선언문이다. 예전의 version: "3" 같은 파일 형식을 골라야 하나 고민할 필요도 줄었다. Docker가 권장하는 현재 기준은 Compose Specification이며, 과거 2.x와 3.x 형식은 이 명세로 통합됐다. 새 파일은 명세의 최상위 요소인 services, 필요에 따라 networks, volumes를 중심으로 시작하면 된다.
먼저 모델을 나눈다: 서비스, 네트워크, 볼륨
services 아래에는 실제로 실행될 컨테이너의 역할을 둔다. 서비스 이름은 같은 네트워크의 다른 컨테이너가 찾는 DNS 이름이 되므로, cache, worker처럼 역할이 드러나는 이름이 좋다. 아무 네트워크도 선언하지 않으면 Compose는 프로젝트 전용 default 네트워크를 만들고 모든 서비스를 연결한다. 작은 로컬 실험에는 편하지만, 외부에 노출되는 프록시와 내부 저장소까지 같은 통신 면에 두는 습관은 오래 갈수록 불편해진다. 공유할 필요가 없는 서비스는 별도의 네트워크에 두고, 두 네트워크가 모두 필요한 중간 서비스만 양쪽에 연결하는 편이 의도를 잘 드러낸다.
volumes는 컨테이너가 교체돼도 유지할 데이터를 위한 이름 있는 저장소다. 최상위 volumes에 한 번 선언하고 필요한 서비스가 명시적으로 마운트해야 한다. 반면 소스 코드를 개발 중에 즉시 반영하려는 호스트 경로는 bind mount가 더 어울린다. 둘을 같은 것으로 취급하면 배포 환경의 경로 의존성이나 데이터 소유권 문제를 뒤늦게 만난다.
아래는 비밀값 없이 구조만 보여 주는 예제다. worker는 cache가 건강 상태를 통과할 때까지 기다린 뒤 시작하고, 두 서비스는 내부 네트워크만 공유한다. app-data는 재시작 뒤에도 남는 이름 있는 볼륨이다.
services:
worker:
image: busybox:1.36
command: ["sh", "-c", "while true; do sleep 3600; done"]
depends_on:
cache:
condition: service_healthy
networks:
- internal
volumes:
- app-data:/var/lib/example
cache:
image: redis:7-alpine
command: ["redis-server", "--save", "", "--appendonly", "no"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
networks:
- internal
networks:
internal:
volumes:
app-data:
실제 서비스에는 이미지 태그, 포트 공개 범위, 읽기 전용 마운트 여부, 리소스 한계 등 운영 맥락을 추가해야 한다. 특히 서비스 간 연결은 localhost가 아니라 상대 서비스 이름과 컨테이너 포트로 잡는다. 컨테이너 안의 localhost는 그 컨테이너 자신이기 때문이다.
depends_on은 준비 완료를 뜻하지 않는다
가장 흔한 오해는 짧은 형식의 depends_on: [cache]가 “캐시를 쓸 수 있을 때까지 기다린다”는 뜻이라는 것이다. Docker Compose가 보장하는 것은 의존 컨테이너가 먼저 시작된다는 순서다. 프로세스가 실행 중이라고 해서 데이터베이스 마이그레이션, 캐시 로딩, TLS 초기화까지 끝났다는 의미는 아니다. 따라서 짧은 형식은 시작 순서가 필요한 정도의 관계에만 쓰고, 연결 가능 상태가 중요하면 의존 서비스에 healthcheck를 정의한 뒤 긴 형식의 condition: service_healthy를 사용한다.
그래도 healthcheck가 애플리케이션 전체의 업무 준비를 자동으로 증명하지는 않는다. 체크 명령은 실제로 무엇을 확인하는지 신중하게 설계해야 하며, 장애·재시작 뒤의 재연결 전략은 애플리케이션에도 필요하다. 초기화 작업이 별도 일회성 서비스라면 service_completed_successfully 조건을 고려할 수 있다. Compose의 순서는 편의 기능이지, 분산 시스템의 복구 정책을 대신하는 기능은 아니다.
실행 전에 렌더링해서 검증한다
YAML 들여쓰기 오류만 찾는 검증으로는 부족하다. Compose는 파일 병합, 환경 변수 치환, 짧은 문법의 정규화를 거쳐 실제 모델을 만든다. 다음 명령을 습관처럼 실행하면 그 결과를 먼저 볼 수 있다.
docker compose -f compose.yaml config
docker compose -f compose.yaml config --quiet
docker compose -f compose.yaml config --services
첫 명령은 Docker Engine에 적용될 정규화된 구성을 출력한다. 두 번째의 --quiet는 출력 없이 유효성만 확인하므로 CI에 적합하다. 마지막 명령은 의도한 서비스 목록이 선택됐는지 빠르게 확인한다. 환경 변수를 쓰는 파일은 로컬의 우연한 .env 값만 믿지 말고, 배포 환경에서 어떤 값이 주입되는지 별도로 검토해야 한다. 비밀값 자체를 YAML이나 저장소에 적지 않는 원칙도 유지한다.
Compose YAML의 좋은 출발점은 “컨테이너를 띄웠다”가 아니라 “어떤 서비스가 어떤 네트워크에서, 어떤 데이터를 보존하며, 어떤 준비 조건 뒤에 실행되는가”에 답하는 것이다. 이 질문에 파일이 명확히 답하면, 개발 환경과 운영 환경의 차이도 훨씬 다루기 쉬워진다.