목. 8월 14th, 2025

G: 안녕하세요! 개발자 여러분, 또는 복잡한 애플리케이션 환경 설정에 골머리를 앓고 계신 모든 분들! 😩

단일 컨테이너는 docker run 명령 하나로 쉽게 띄울 수 있지만, 웹 서버, 데이터베이스, 캐시, 메시지 큐 등 여러 개의 서비스가 유기적으로 연결된 복잡한 애플리케이션을 구축할 때는 어떠셨나요?

각 컨테이너마다 일일이 docker run 명령어를 입력하고, 네트워크를 설정하고, 볼륨을 연결하는 과정은 생각만 해도 아찔합니다. 마치 레고 블록 하나하나를 손으로 조립하는 느낌이랄까요? 🤯

하지만 걱정 마세요! 오늘 제가 소개해 드릴 Docker Compose는 이 복잡한 멀티 컨테이너 환경을 마치 마법처럼 한 방에 정리해 줄 수 있는 강력한 도구입니다. 이 글을 끝까지 읽으시면, 여러분의 개발 환경 설정이 훨씬 더 빠르고, 효율적이며, 스트레스 없이 바뀔 것입니다! 🎉


1. 🤯 복잡한 멀티 컨테이너, 왜 골칫거리일까요?

우리가 개발하는 현대적인 웹 애플리케이션은 대부분 여러 컴포넌트로 구성됩니다. 예를 들어:

  • 프론트엔드 웹 서버: Nginx, Apache, Node.js (React/Vue/Angular)
  • 백엔드 API 서버: Python (Django/Flask), Java (Spring Boot), Node.js (Express), Go
  • 데이터베이스: PostgreSQL, MySQL, MongoDB
  • 캐시/메시지 큐: Redis, RabbitMQ, Kafka
  • 기타: ElasticSearch, Prometheus 등

이 컴포넌트들을 Docker 컨테이너로 각각 실행하려면, 다음과 같은 문제에 직면합니다.

  1. 길고 복잡한 docker run 명령: 각 컨테이너마다 포트 포워딩, 볼륨 마운트, 환경 변수 설정, 네트워크 연결 등 수많은 옵션을 일일이 지정해야 합니다. 😵
  2. 의존성 관리: 데이터베이스 컨테이너가 먼저 실행되어야 백엔드 서버가 제대로 시작될 수 있는데, 이 순서를 수동으로 맞춰주는 것도 일입니다.
  3. 네트워크 설정: 컨테이너 간 통신을 위해 별도의 네트워크를 생성하고 연결해야 합니다.
  4. 재현성 부족: 다른 팀원이나 새로운 환경에서 이 복잡한 설정을 그대로 재현하기 어렵습니다. “내 PC에서는 잘 되는데…” 라는 말을 듣기 쉽죠. 🤦‍♀️

Docker Compose는 이러한 문제들을 깔끔하게 해결해 줍니다!


2. ✨ Docker Compose란 무엇일까요?

Docker Compose다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구입니다. 쉽게 말해, 여러 개의 컨테이너를 하나의 서비스 묶음으로 정의하고, 이를 한 번의 명령으로 관리할 수 있게 해줍니다.

핵심은 바로 YAML 파일입니다. docker-compose.yml이라는 파일 하나에 모든 서비스의 구성 (어떤 이미지를 사용할지, 어떤 포트를 열지, 어떤 볼륨을 연결할지, 어떤 환경 변수를 쓸지 등)을 선언적(Declarative)으로 정의합니다.

🤔 왜 필요할까요?

  • 간편한 환경 설정: 복잡한 docker run 명령을 외우거나 반복할 필요 없이, YAML 파일 하나로 끝!
  • 쉬운 재현성: docker-compose.yml 파일만 공유하면, 누구든 동일한 환경을 쉽게 구축할 수 있습니다. “Working on my machine!” 대신 “Working on our machines!” 😉
  • 통합 관리: 모든 서비스를 한 번에 시작, 중지, 재시작할 수 있습니다.
  • 컨테이너 간 의존성 및 네트워크 관리 자동화: 각 서비스 간의 통신과 시작 순서 등을 Compose가 알아서 처리해 줍니다.

3. 🧩 Docker Compose 핵심 개념 파헤치기

docker-compose.yml 파일을 작성하기 전에, 몇 가지 핵심 개념을 알아두면 좋습니다.

3.1. services (서비스)

docker-compose.yml 파일의 핵심입니다. 애플리케이션을 구성하는 각각의 컨테이너 단위를 ‘서비스’라고 정의합니다. 예를 들어, web, db, redis 등이 서비스가 될 수 있습니다.

services:
  web: # 웹 서버 서비스
    ...
  db:  # 데이터베이스 서비스
    ...

3.2. image 또는 build (이미지 또는 빌드)

서비스에 사용할 Docker 이미지를 지정합니다.

  • image: Docker Hub 등 기존에 만들어진 이미지를 사용합니다. (예: nginx:latest, postgres:14)
  • build: 프로젝트 내의 Dockerfile을 이용해 이미지를 직접 빌드합니다. (예: ./backend 경로의 Dockerfile)
services:
  web:
    build: ./webapp # webapp 폴더의 Dockerfile로 이미지 빌드
  db:
    image: postgres:14 # postgres:14 이미지 사용

3.3. ports (포트 매핑)

호스트 머신의 포트와 컨테이너 내부의 포트를 연결합니다. 호스트포트:컨테이너포트 형식입니다.

services:
  web:
    ports:
      - "80:80" # 호스트의 80번 포트를 컨테이너의 80번 포트에 연결
      - "443:443"

3.4. volumes (볼륨 마운트)

컨테이너와 호스트 머신 간에 데이터를 공유하거나 컨테이너 내부의 데이터를 영속적으로 저장하기 위해 사용합니다.

  • 바인드 마운트 (Bind Mount): 호스트경로:컨테이너경로 형식으로 호스트 파일 시스템의 특정 경로를 컨테이너에 마운트합니다. 개발 중 소스 코드 변경 시 유용합니다.
  • 명명된 볼륨 (Named Volume): 볼륨이름:컨테이너경로 형식으로 Docker가 관리하는 볼륨을 사용합니다. 데이터베이스 데이터 등 영구적인 데이터 저장에 적합합니다.
services:
  web:
    volumes:
      - ./app:/usr/src/app # 현재 폴더의 app을 컨테이너의 /usr/src/app에 마운트 (바인드 마운트)
  db:
    volumes:
      - db_data:/var/lib/postgresql/data # db_data라는 이름의 볼륨을 사용 (명명된 볼륨)

volumes: # 최상위 레벨에서 명명된 볼륨 정의
  db_data:

3.5. environment (환경 변수)

컨테이너 내부에 필요한 환경 변수를 설정합니다. 민감한 정보는 .env 파일을 활용하는 것이 좋습니다.

services:
  db:
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password

3.6. depends_on (의존성)

서비스 간의 의존성을 정의하여 특정 서비스가 다른 서비스보다 먼저 시작되도록 합니다. 주의: 이는 “시작 순서”만 보장하며, 서비스가 “준비 완료”되었음을 보장하지는 않습니다. (예: DB 컨테이너가 시작되었지만, DB 서버 프로세스가 완전히 준비되기 전까지는 연결 불가) 이를 위해 healthcheck를 함께 사용하는 것이 좋습니다.

services:
  web:
    depends_on:
      - db # web 서비스는 db 서비스가 시작된 후에 시작
      - redis

3.7. networks (네트워크)

컨테이너 간 통신을 위한 가상 네트워크를 정의하고 연결합니다. 기본적으로 Docker Compose는 모든 서비스를 위한 기본 네트워크를 생성하지만, 명시적으로 정의하여 더 세분화된 네트워크를 구성할 수 있습니다.

services:
  web:
    networks:
      - app_network
  db:
    networks:
      - app_network

networks:
  app_network: # 최상위 레벨에서 네트워크 정의
    driver: bridge

4. 🛠️ 시작하기: Docker Compose 설치 (feat. Docker Desktop)

Docker Compose는 대부분의 Docker 설치에 기본으로 포함되어 있습니다.

  • Docker Desktop (Windows/macOS): Docker Desktop을 설치하면 Docker CLI와 함께 Docker Compose CLI가 자동으로 설치됩니다. 별도의 설치가 필요 없습니다! 👍
  • Linux: Docker 엔진 설치 시 함께 설치되거나, 별도로 pip install docker-compose 또는 패키지 관리자를 통해 설치해야 할 수 있습니다. 최신 Docker 버전에서는 docker compose (하이픈 없는 버전) 명령어가 기본으로 내장되어 있습니다.

설치 확인은 터미널에서 다음 명령어를 입력해 보세요.

docker compose version
# 또는 (구 버전)
docker-compose --version

정상적으로 버전 정보가 출력되면 준비 완료입니다!


5. 🚀 실전 예제: 웹 + DB 애플리케이션 빌드하기

이제 실제로 Docker Compose를 사용하여 간단한 웹 애플리케이션과 PostgreSQL 데이터베이스를 함께 띄워보겠습니다.

시나리오:

  • 웹 애플리케이션: 파이썬 Flask 기반의 아주 간단한 웹 서버 (컨테이너 내부 5000번 포트 사용)
  • 데이터베이스: PostgreSQL (컨테이너 내부 5432번 포트 사용)

5.1. 프로젝트 구조 만들기

먼저, 프로젝트 폴더를 만들고 필요한 파일들을 생성합니다.

my-web-app/
├── webapp/
│   ├── Dockerfile
│   └── app.py
│   └── requirements.txt
└── docker-compose.yml

5.2. webapp/Dockerfile 작성 (웹 애플리케이션 이미지 빌드용)

# webapp/Dockerfile
FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

5.3. webapp/requirements.txt 작성 (웹 애플리케이션 의존성)

# webapp/requirements.txt
Flask
psycopg2-binary # PostgreSQL 연결용 드라이버

5.4. webapp/app.py 작성 (간단한 Flask 웹 애플리케이션)

# webapp/app.py
from flask import Flask, jsonify
import psycopg2
import os
import time

app = Flask(__name__)

# 환경 변수에서 DB 연결 정보 가져오기
DB_HOST = os.getenv('DB_HOST', 'db') # Docker Compose 서비스 이름 'db'
DB_NAME = os.getenv('POSTGRES_DB', 'mydatabase')
DB_USER = os.getenv('POSTGRES_USER', 'user')
DB_PASSWORD = os.getenv('POSTGRES_PASSWORD', 'password')

def get_db_connection():
    conn = None
    max_retries = 10
    retry_delay = 5 # seconds
    for i in range(max_retries):
        try:
            conn = psycopg2.connect(
                host=DB_HOST,
                database=DB_NAME,
                user=DB_USER,
                password=DB_PASSWORD
            )
            print("데이터베이스 연결 성공!")
            return conn
        except psycopg2.OperationalError as e:
            print(f"데이터베이스 연결 실패 ({i+1}/{max_retries}): {e}")
            if i < max_retries - 1:
                print(f"{retry_delay}초 후 재시도...")
                time.sleep(retry_delay)
            else:
                raise
    return None

@app.route('/')
def hello():
    return "안녕하세요, Docker Compose로 구동되는 웹 앱입니다! 👋"

@app.route('/db')
def db_test():
    try:
        conn = get_db_connection()
        if conn:
            cursor = conn.cursor()
            cursor.execute("SELECT version();")
            db_version = cursor.fetchone()[0]
            cursor.close()
            conn.close()
            return jsonify({"status": "success", "message": "DB 연결 및 조회 성공!", "db_version": db_version})
        else:
            return jsonify({"status": "error", "message": "DB 연결 실패!"}), 500
    except Exception as e:
        return jsonify({"status": "error", "message": f"오류 발생: {str(e)}"}), 500

if __name__ == '__main__':
    # DB 컨테이너가 완전히 준비될 때까지 기다리기 (간단한 예시)
    # 실제 프로덕션에서는 healthcheck나 초기화 스크립트 사용 권장
    print("웹 앱 시작 중... DB 연결 대기...")
    while True:
        try:
            conn = get_db_connection()
            if conn:
                conn.close()
                break
        except Exception as e:
            print(f"DB 연결 시도 중 오류: {e}")
            time.sleep(5) # 5초 대기 후 재시도
    print("DB 연결 확인 완료! 웹 앱 시작.")
    app.run(host='0.0.0.0', port=5000, debug=True)

Tip: DB_HOSTdb로 설정한 이유는 Docker Compose가 자동으로 서비스 이름으로 컨테이너 간에 DNS 이름을 생성해주기 때문입니다. 별도의 IP 주소를 알 필요 없이 서비스 이름으로 통신할 수 있습니다.

5.5. docker-compose.yml 작성 (핵심!)

# docker-compose.yml
version: '3.8' # Docker Compose 파일 형식 버전

services:
  web: # 웹 애플리케이션 서비스
    build: ./webapp # 현재 디렉토리의 webapp 폴더에 있는 Dockerfile로 이미지 빌드
    ports:
      - "80:5000" # 호스트의 80번 포트를 컨테이너의 5000번 포트에 연결
    volumes:
      - ./webapp:/app # 개발 중 코드 변경 시 자동 반영을 위해 소스 코드 마운트
    environment: # 환경 변수 설정
      DB_HOST: db # DB 서비스의 호스트 이름 (docker-compose 내부 DNS 사용)
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    depends_on: # db 서비스가 먼저 시작되어야 함을 명시
      - db
    networks:
      - my_app_network # my_app_network 에 연결

  db: # PostgreSQL 데이터베이스 서비스
    image: postgres:14 # Docker Hub에서 postgres:14 이미지 사용
    ports:
      - "5432:5432" # (선택 사항) 호스트에서 DB에 직접 접근하고 싶을 때 사용
    environment: # DB 환경 변수 설정
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      PGDATA: /var/lib/postgresql/data/pgdata # DB 데이터 파일 경로
    volumes:
      - db_data:/var/lib/postgresql/data # DB 데이터를 영속적으로 저장하기 위한 명명된 볼륨
    networks:
      - my_app_network # my_app_network 에 연결

networks: # 커스텀 네트워크 정의 (선택 사항이지만 권장)
  my_app_network:
    driver: bridge

volumes: # 명명된 볼륨 정의
  db_data:

5.6. 애플리케이션 실행하기!

모든 파일이 준비되었다면, my-web-app 폴더 (즉, docker-compose.yml 파일이 있는 위치)에서 터미널을 열고 다음 명령어를 입력합니다.

docker compose up -d
  • up: docker-compose.yml 파일에 정의된 모든 서비스를 시작합니다.
  • -d: detached 모드로 백그라운드에서 컨테이너를 실행합니다. (터미널이 자유로워집니다!)

명령어를 실행하면, Docker Compose가 자동으로 webapp 이미지 빌드 -> db 컨테이너 시작 -> web 컨테이너 시작 순서로 모든 것을 처리합니다. 처음 실행 시에는 이미지 다운로드 및 빌드 시간이 소요될 수 있습니다.

5.7. 확인하기

컨테이너가 잘 실행되고 있는지 확인해봅시다.

docker compose ps

아마 다음과 비슷한 출력을 볼 수 있을 거예요.

NAME                COMMAND                  SERVICE             STATUS              PORTS
my-web-app-db-1     "docker-entrypoint.s…"   db                  running             0.0.0.0:5432->5432/tcp
my-web-app-web-1    "python app.py"          web                 running             0.0.0.0:80->5000/tcp

running 상태를 확인했다면 성공입니다! 🎉

이제 웹 브라우저를 열고 다음 주소로 접속해 보세요.

  • http://localhost/ : “안녕하세요, Docker Compose로 구동되는 웹 앱입니다! 👋” 메시지를 볼 수 있습니다.
  • http://localhost/db : “DB 연결 및 조회 성공!” 메시지와 함께 PostgreSQL 버전 정보를 볼 수 있습니다.

5.8. 유용한 Docker Compose 명령어들

  • 모든 서비스 중지 및 컨테이너 제거:
    docker compose down

    이 명령은 컨테이너뿐만 아니라 기본 네트워크도 제거합니다. -v 옵션을 추가하면 명명된 볼륨까지 제거할 수 있습니다. (데이터베이스 데이터 등 모든 데이터가 사라지므로 주의!)

    docker compose down -v
  • 서비스 재시작:
    docker compose restart [서비스 이름] # 특정 서비스만 재시작
    docker compose restart # 모든 서비스 재시작
  • 서비스 로그 확인:
    docker compose logs -f [서비스 이름] # 특정 서비스의 실시간 로그 확인
    docker compose logs # 모든 서비스의 로그 확인
  • 특정 서비스만 시작/재시작:
    docker compose up -d web # web 서비스만 시작
  • 이미지 재빌드 (코드 변경 후):
    docker compose build web # web 서비스의 이미지를 다시 빌드
    docker compose up -d --build # 모든 서비스 이미지를 다시 빌드하고 실행

6. 💡 고급 팁 & 베스트 프랙티스

Docker Compose를 더 효율적으로 사용하기 위한 몇 가지 팁입니다.

6.1. 개발/운영 환경 분리 (docker-compose.override.yml)

개발 환경에서는 소스 코드를 바인드 마운트하고 디버그 모드를 활성화하는 등 개발 편의 기능을 사용하고 싶을 수 있습니다. 하지만 운영 환경에서는 안정성과 성능을 위해 빌드된 이미지를 사용하고 디버그 모드는 끄는 것이 좋습니다.

이때 docker-compose.yml (기본 설정)과 docker-compose.override.yml (재정의/확장 설정) 파일을 분리하여 사용할 수 있습니다. docker compose up 명령은 기본적으로 이 두 파일을 함께 로드합니다.

예시:

  • docker-compose.yml: 공통적인 이미지, 포트, 네트워크 정의
  • docker-compose.override.yml: (개발용) 소스 코드 바인드 마운트, 추가 디버그 포트, 개발 환경 변수 등
# docker-compose.yml (기본)
version: '3.8'
services:
  web:
    build: .
    ports:
      - "80:80"
    environment:
      NODE_ENV: production # 기본은 운영 환경

# docker-compose.override.yml (개발 환경에서만 로드)
version: '3.8'
services:
  web:
    volumes:
      - ./app:/usr/src/app # 개발 시 소스 코드 실시간 반영
    environment:
      NODE_ENV: development # 개발 환경
      DEBUG: 'true'

docker compose up 하면 두 파일의 설정이 합쳐져 web 서비스가 development 환경으로 DEBUG가 켜진 채로 /app 볼륨이 마운트되어 실행됩니다.

6.2. .env 파일로 환경 변수 관리

민감한 정보(비밀번호, API 키 등)나 자주 바뀌는 설정은 .env 파일에 저장하고, docker-compose.yml에서 이를 참조할 수 있습니다. .env 파일은 Git 저장소에 올리지 않도록 .gitignore에 추가하는 것이 중요합니다.

예시:

  • .env 파일:
    POSTGRES_PASSWORD=my_secure_password
    API_KEY=your_api_key_here
  • docker-compose.yml 파일:
    services:
      db:
        environment:
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # .env 파일에서 가져옴

6.3. Health Check (헬스 체크)

depends_on은 시작 순서만 보장합니다. 서비스가 “정말로 준비되었는지” 확인하려면 healthcheck를 사용해야 합니다.

services:
  db:
    image: postgres:14
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydatabase"]
      interval: 5s
      timeout: 5s
      retries: 5
    environment:
      # ... DB 환경 변수 ...
  web:
    build: ./webapp
    depends_on:
      db:
        condition: service_healthy # db 서비스가 healthy 상태일 때까지 대기

이렇게 설정하면 web 서비스는 db 서비스가 healthy 상태가 될 때까지 기다렸다가 시작됩니다.

6.4. restart 정책 설정

컨테이너가 예기치 않게 종료되었을 때 자동으로 다시 시작되도록 설정할 수 있습니다.

  • no: 재시작 안 함 (기본값)
  • on-failure: 비정상 종료 시에만 재시작
  • always: 항상 재시작 (Docker 데몬 시작 시에도)
  • unless-stopped: 수동으로 중지하지 않는 한 항상 재시작
services:
  web:
    restart: unless-stopped

6.5. Profiles (프로파일)

특정 환경에서만 필요한 서비스를 정의하고 싶을 때 사용합니다. 예를 들어, test 프로파일을 만들고 테스트용 데이터베이스 서비스만 포함시킬 수 있습니다.

# docker-compose.yml
services:
  web:
    # ...
  db:
    # ...
  adminer: # 관리자 웹 UI (개발 환경에서만 필요)
    profiles: ["dev"] # dev 프로파일이 활성화될 때만 이 서비스 포함
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db

실행할 때는 docker compose --profile dev up -d 와 같이 adminer 서비스만 함께 실행할 수 있습니다.


7. 🎯 언제 Docker Compose를 사용할까요?

Docker Compose는 모든 상황에 맞는 만능 해결책은 아닙니다. 하지만 다음과 같은 상황에서 빛을 발합니다.

  • 개발 환경 구축: 로컬에서 여러 서비스로 구성된 애플리케이션을 빠르게 띄우고 싶을 때 가장 이상적입니다. 팀원 간에 동일한 개발 환경을 공유하기 매우 좋습니다.
  • 테스트 환경: CI/CD 파이프라인에서 통합 테스트를 실행하기 위한 환경을 구성할 때 유용합니다. 테스트가 끝나면 환경을 쉽게 정리할 수 있습니다.
  • 소규모 프로덕션 배포: 비교적 단순하고 트래픽이 많지 않은 단일 서버에서 웹 앱, 블로그, 개인 프로젝트 등 소규모 서비스를 배포할 때 충분히 강력합니다.
  • 개념 증명(PoC) 또는 데모: 복잡한 시스템의 동작 방식을 빠르게 시연하거나 프로토타입을 만들 때 좋습니다.

8. 🚧 Docker Compose의 한계점

Docker Compose는 개발 및 소규모 배포에 강력하지만, 몇 가지 한계점도 명확합니다.

  • 단일 호스트에 국한: Docker Compose는 기본적으로 하나의 호스트 머신 위에서만 동작합니다. 여러 서버에 걸쳐 서비스를 분산하거나 고가용성을 제공하지 않습니다. (이를 위해서는 Kubernetes, Docker Swarm 같은 컨테이너 오케스트레이션 도구가 필요합니다.)
  • 확장성 및 로드 밸런싱 부족: 서비스의 인스턴스를 동적으로 늘리거나 로드 밸런싱을 제공하지 않습니다.
  • 자동 복구 및 스케줄링 미지원: 컨테이너 실패 시 자동 복구, 자원 할당에 따른 컨테이너 배치 등 고급 오케스트레이션 기능은 제공하지 않습니다.

따라서 대규모, 고가용성, 고성능이 요구되는 프로덕션 환경에서는 Docker Compose 단독 사용보다는 Kubernetes와 같은 전문적인 오케스트레이션 도구를 고려해야 합니다.


9. 💖 마무리하며

오늘 우리는 복잡한 멀티 컨테이너 환경을 docker-compose.yml 파일 하나로 깔끔하게 정리해주는 마법 같은 도구, Docker Compose에 대해 자세히 알아보았습니다.

이제 여러분은 더 이상 수많은 docker run 명령어의 늪에서 헤맬 필요가 없습니다. 웹 서버, 데이터베이스, 캐시 등 여러 서비스가 얽혀있는 환경도 docker compose up 명령어 하나로 간편하게 구축하고 관리할 수 있게 된 것입니다! 🥳

Docker Compose는 개발 생산성을 비약적으로 향상시키고, 팀 협업을 원활하게 만들어주는 필수 도구입니다. 이 글에서 배운 내용을 바탕으로 여러분의 프로젝트에 Docker Compose를 적극적으로 활용해 보세요. 분명 놀라운 변화를 경험하실 겁니다!

궁금한 점이 있다면 언제든지 댓글로 질문해주세요! 다음에는 더 유익한 내용으로 찾아오겠습니다. 해피 코딩! 💻✨

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다