Docker Compose YAML 튜토리얼: 멀티 서비스 배포를 위한 완벽 가이드

현대적인 소프트웨어 개발 환경은 더 이상 단일 애플리케이션으로 끝나지 않습니다. 마이크로서비스 아키텍처(MSA)가 표준이 되면서, 하나의 애플리케이션을 구동하기 위해 웹 서버, 데이터베이스, 캐시 레이어, 메시지 브로커 등 여러 개의 컨테이너가 동시에 유기적으로 움직여야 합니다. 이때 각 컨테이너를 일일이 docker run 명령어로 실행하고 네트워크를 연결하는 것은 불가능에 가깝습니다.

이러한 복잡성을 해결하기 위해 등장한 도구가 바로 Docker Compose입니다. 본 Docker Compose YAML 튜토리얼에서는 YAML 파일을 사용하여 여러 컨테이너를 하나의 정의된 설정으로 관리하고, 단 한 번의 명령으로 전체 스택을 배포하는 방법을 깊이 있게 다룹니다.


1. Docker Compose와 YAML의 역할 이해하기

Docker Compose는 다중 컨테이너 애플리케이션을 정의하고 실행하기 위한 도구입니다. YAML(YAML Ain't Markup Language)은 이 도구의 핵심 언어로, 사람이 읽기 쉽고 기계가 파싱하기 용이한 데이터 직렬화 형식을 제공합니다.

Docker Compose의 핵심 개념

Docker Compose의 핵심은 '선언적(Declarative) 구성'에 있습니다. 사용자가 "어떤 컨테이너를 어떤 네트워크에 연결하고, 어떤 볼륨을 사용할 것인가"를 YAML 파일에 선언해두면, Docker Compose는 현재 상태를 사용자가 정의한 상태와 일치시키기 위해 필요한 모든 작업을 자동으로 수행합니다.

왜 YAML을 사용하는가?

YAML은 구조가 단순하고 계층적입니다. 이는 복잡한 인프라 설정을 트리 구조로 표현하기에 최적입니다. 예를 들어, 특정 서비스 아래에 환경 변수, 포트 매핑, 볼기(Volume) 설정을 논리적으로 그룹화할 수 있습니다. 이러한 구조적 이점 덕분에 개발자는 복잡한 쉘 스크립트 없이도 인프라를 코드로 관리(Infrastructure as Code, IaC)할 수 있습니다.

멀티 서비스 오케스트레이션의 이점

여러 서비스를 하나의 docker-compose.yml 파일로 관리하면 다음과 같은 이점이 있습니다. * 재현성: 개발, 테스트, 운영 환경에서 동일한 환경을 즉시 구축할 수 있습니다. * 격리 및 통신: 서비스 간 전용 네트워크를 구성하여 보안을 강화하고 서비스 이름만으로 통신이 가능하게 합니다. * 효율성: docker-compose up 명령어 하나로 전체 시스템의 생명주기를 관리합니다.


2. Docker Compose YAML 파일의 핵심 구조 분석

성공적인 배포를 위해서는 YAML 파일의 각 섹션이 어떤 역할을 하는지 정확히 이해해야 합니다.

Versioning (버전 정의)

파일의 최상단에는 version을 명시합니다. 이는 Docker Compose 엔진이 파일을 해석할 때 사용할 문법 규격을 결정합니다. 최근 버전에서는 버전 명시를 생략하기도 하지만, 명시적으로 작성하는 것이 호환성 측로 안전합니다.

Services (서비스 정의)

가장 중요한 섹션입니다. 애플리케이션을 구성하는 각 컨테이너를 하나의 '서비스'로 정의합니다. 각 서비스는 어떤 이미지를 사용할지, 어떤 포트를 열지, 어떤 환경 변수를 가질지를 포함합니다.

Networks (네트워크 구성)

서비스들이 서로 통신할 수 있는 논리적 네트워크를 정의합니다. 기본적으로 Docker Compose는 모든 서비스가 참여하는 기본 네트워크를 생성하지만, 특정 서비스 그룹만 접근 가능하도록 격리된 네트워크를 설계할 수 있습니다.

Volumes (데이터 지속성)

컨테이너는 삭제되면 내부 데이터도 사라집니다. 데이터베이스의 데이터나 로그 파일처럼 영구적으로 보존해야 하는 데이터를 위해 호스트 머신의 디렉토리나 명명된 볼륨(Named Volume)을 연결합니다.


3. [실전 튜토리얼] Python 웹 앱과 Redis 배포하기

이제 실제 사례를 통해 docker-compose.yml 파일을 작성하고 배포하는 과정을 살펴보겠습니다. 우리의 목표는 Python(Flask) 애플리케이션이 Redis 캐시 서버를 사용하여 데이터를 저장하는 환경을 구축하는 것입니다.

시나리오 설정

  1. Web 서비스: Flask 기반의 웹 애플리케기 (Port 5000)
  2. Redis 서비스: 캐시 저장소로 사용되는 Redis (Port 6379)
  3. 네트워크: 두 서비스가 서로 통신할 수 있는 app-network 구축

Docker Compose YAML 코드 예제

version: '3.8'

services:
  # 1. 웹 애플리케이션 서비스 정의
  web-app:
    build: .  # 현재 디렉토리의 Dockerfile을 사용하여 이미지 빌드
    ports:
      - "5000:5000"  # 호스트 5000번 포트와 컨테이너 5000번 포트 연결
    environment:
      - REDIS_HOST=redis-server  # Redis 서비스 이름을 호스트로 사용
      - FLASK_ENV=development
    depends_on:
      - redis-server  # redis-server가 먼저 실행된 후 실행되도록 설정
    networks:
      - backend-network

  # 2. Redis 캐시 서비스 정의
  redis-server:
    image: "redis:alpine"  # Docker Hub에서 공식 redis 이미지 다운로드
    ports:
      - "6379:6379"
    networks:
      - backend-network
    volumes:
      - redis-data:/data  # 데이터 영속성을 위한 볼륨 설정

# 3. 네트워크 및 볼륨 설정
networks:
  backend-network:
    driver: bridge

volumes:
  redis-data:

코드 상세 설명

  • build: .: 이미 만들어진 이미지를 쓰는 대신, 현재 폴더의 Dockerfile을 읽어 직접 이미지를 만듭니다. 이는 커스텀 로직이 포함된 앱에 필수적입니다.
  • depends_on: 이 설정은 매우 중요합니다. web-app이 실행되기 전에 redis-server가 먼저 구동되도록 순서를 보장합니다. 만약 이 설정이 없다면, Redis가 준비되지 않은 상태에서 앱이 실행되어 연결 오류가 발생할 수 있습니다.
  • networks: backend-network라는 별도의 네트워크를 생성하여 web-app과 redis-server만 이 네트워크에 참여하게 함으로써, 외부로부터의 불필요한 접근을 차단합니다.
  • volumes: redis-data라는 이름의 볼륨을 생성하여 컨테이너가 재시작되거나 삭제되어도 Redis의 데이터가 유지되도록 합니다.

더 자세한 Docker Compose 명령어 가이드를 통해 이 설정을 실행하고 관리하는 방법을 익혀보세요.


4. 고급 설정: 의존성 관리와 환경 변수 분리

단순한 배포를 넘어, 운영 환경에 적합한 견고한 시스템을 구축하려면 고급 기술이 필요합니다.

환경 변수의 외부화 (.env 활용)

YAML 파일에 비밀번호나 API 키를 직접 적는 것은 보안상 매우 위험합니다. 대신 .env 파일을 사용하여 민감한 정보를 분리해야 합니다.

.env 파일 예시:

DB_PASSWORD=supersecretpassword
DEBUG_MODE=true

docker-compose.yml 수정:

services:
  db:
    image: postgres
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}

이렇게 하면 소스 코드를 공유할 때 비밀번호를 노출하지 않고도 각 개발자가 자신만의 환경 설정을 가질 수 있습니다.

Healthcheck를 통한 안정성 확보

depends_on은 컨테이너의 '실행' 여부만 확인합니다. 하지만 컨테이너가 실행 중이라도 내부의 데이터베이스 엔진이 아직 준비되지 않았을 수 있습니다. 이때 healthcheck를 사용하면 서비스가 실제로 '사용 가능한 상태'인지 확인하고 다음 서비스를 실행할 수 있습니다.

services:
  redis-server:
    image: "redis:alpine"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

5. Docker Run vs Docker Compose: 비교 분석

많은 입문자가 "그냥 docker run을 쓰면 안 되나요?"라고 묻습니다. 아래 표를 통해 두 방식의 차이점을 명확히 이해할 수 있습니다.

비교 항목 Docker Run (Single Container) Docker Compose (Multi-Service)
관리 대상 단일 컨테이너 여러 컨테이너의 집합 (Stack)
설정 방식 명령행 인자(CLI)로 매번 입력 YAML 파일에 선언적으로 정의
네트워크 구성 --network 옵션으로 수동 연결 서비스 간 자동 DNS 및 네트워크 격리
의존성 관리 실행 순서를 사용자가 직접 제어 depends_on으로 실행 순서 자동화
재현성 환경 구축 시 명령어를 기억해야 함 파일 하나로 동일 환경 즉시 재현 가능
적합한 용도 간단한 테스트, 단일 유틸리티 실행 복잡한 웹 애플리케이션, 마이크로서비스

결론적으로, 단순한 테스트에는 docker run이 빠를 수 있지만, 실제 서비스 구조를 설계할 때는 Super Tools의 개발자 유틸리티와 같은 도구들과 함께 Docker Compose를 사용하는 것이 표준입니다.


6. 운영 환경을 위한 Docker Compose 모범 사례

프로덕션 환경에서 Docker Compose를 사용할 때는 다음의 원칙을 준데해야 합니다.

1. 이미지 태그를 고정하라 (Avoid latest)

image: node:latest와 같이 latest 태그를 사용하면, 어느 날 갑자기 업데이트된 이미지가 내려받아져 애플리케이션이 깨질 수 있습니다. 반드시 node:18.16.0처럼 특정 버전을 명시하세요.

2. 최소 권한 원칙 준수

모든 컨테이너를 root 사용자로 실행하지 마세요. Dockerfile 단계에서 USER를 지정하여 보안 취약점을 최소화해야 합니다.

3. 로그 관리 전략 수립

여러 컨테이너에서 발생하는 로그를 한곳에서 확인하기 어렵습니다. logging 드라이버를 설정하여 로그의 크기를 제한(rotation)하거나 외부 로그 수집 시스템(ELK Stack 등)으로 전송하도록 구성하세요.

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

FAQ: 자주 묻는 질문

Q1. docker-compose up과 docker-compose up -d의 차이는 무엇인가요? A1. -d 옵션은 'Detached mode'를 의미합니다. 이 옵션을 사용하면 컨테이너를 백그라운드에서 실행하며, 현재 터미널을 계속 사용할 수 있습니다. 옵션이 없으면 컨테이너의 로그가 터미선에 실시간으로 출력되며, 터미널을 종료하면 컨테이너도 중지될 수 있습니다.

Q2. .env 파일은 어디에 위치해야 하나요? A2. 기본적으로 docker-compose.yml 파일과 동일한 디렉토리에 위치해야 Docker Compose가 자동으로 인식합니다.

Q3. 컨테이너를 삭제할 때 데이터도 같이 삭제되나요? A3. docker-compose down 명령어를 사용하면 컨테이너와 네트워크는 삭제되지만, YAML 파일에 volumes로 정의된 명명된 볼륨(Named Volume)은 삭제되지 않고 유지됩니다. 만약 볼륨까지 삭제하고 싶다면 docker-compose down -v 명령어를 사용해야 합니다.

Q4. depends_on이 서비스의 완전한 준비를 보장하나요? A4. 아니요. depends_on은 컨테이너의 '시작' 순서만 보장합니다. 데이터베이스 내부의 초기화 프로세스가 완료되었는지는 알 수 없으므로, 앞서 설명한 healthcheck와 함께 사용하는 것이 가장 안전합니다.

Q5. 여러 개의 YAML 파일을 사용할 수 있나요? A5. 네, 가능합니다. -f 옵션을 사용하여 여러 파일을 병합할 수 있습니다. 예를를 들어 docker-compose.yml(기본 설정)과 docker-compose.prod.yml(운영 환경 전용 설정)을 결합하여 사용할 수 있습니다.

Q6. 컨테이너 내부의 파일을 호스트에서 수정하고 싶을 때는 어떻게 하나요? A6. volumes 설정을 사용하세요. 호스트의 특정 경로를 컨테이너 내부 경로와 바인드 마운트(Bind Mount)하면, 호스트에서 파일을 수정하는 즉시 컨테팅 내부에도 반영됩니다.


결론

Docker Compose YAML은 단순한 설정 파일을 넘어, 애플리케이션의 인프라를 코드로 정의하는 강력한 도구입니다. 서비스 간의 관계, 네트워크 격리, 데이터의 영속성, 그리고 환경 변수 관리까지 이 파일 하나에 담을 수 있습니다.

이 튜토리얼에서 다룬 의존성 관리(depends_on), 네트워크 분리, 환경 변수 분리, 그리고 볼륨 활용 기술을 숙달한다면, 여러분은 어떤 복잡한 마이크로서비스 환경이라도 안정적이고 재현 가능한 방식으로 배포할 수 있는 능력을 갖추게 될 것입니다. 이제 여러분의 프로젝트에 Docker Compose를 도입하여 더욱 효율적인 개발 워크플로우를 구축해 보세요.