목. 8월 14th, 2025

G: 개발 환경 설정, 지긋지긋하신가요? 😫 새로운 프로젝트를 시작할 때마다 “내 컴퓨터에서는 되는데…”를 외치게 만들었던 그 복잡한 종속성 설치와 버전 충돌의 악몽! 😱 데이터베이스, 웹 서버, 캐시 서버 등 여러 컴포넌트를 일일이 설치하고 설정하는 데 시간을 낭비하셨다면, 오늘부터 그 고통은 끝입니다!

오늘은 바로 Docker Compose를 이용해 로컬 개발 환경을 초고속으로, 그리고 아주 일관성 있게 구축하는 방법을 자세히 알아보겠습니다. 이제 “개발 환경 세팅에만 하루 다 썼네…” 하는 푸념은 옛말이 될 거예요! 🥳


💡 왜 Docker Compose여야 할까요?

기존 로컬 개발 환경 설정 방식은 여러 문제를 안고 있었습니다:

  • 종속성 지옥: 특정 버전의 Node.js, Python, Java, 특정 데이터베이스 등 수많은 소프트웨어를 설치하다 보면 버전 충돌이 빈번하게 발생합니다. 💥
  • 환경 불일치: 개발자마다 OS가 다르고, 설치된 라이브러리 버전이 달라 “내 컴퓨터에서는 잘 되는데?” 문제가 발생합니다. 팀원 간 협업을 방해하는 주범이죠. 😠
  • 복잡한 설정: 여러 컴포넌트(백엔드, 프론트엔드, DB, 캐시 등)를 함께 구동하려면 각자의 설정 파일을 만들고 포트를 맞춰주는 등 번거로운 작업이 필요합니다. 🤯
  • 재현 불가능: 새로운 개발자가 합류하거나 새 컴퓨터를 구매했을 때, 기존 환경을 그대로 재현하는 것이 매우 어렵습니다. 📉

Docker Compose는 이러한 문제를 한 방에 해결해 줍니다! 🎉

  • 일관된 환경: docker-compose.yml 파일 하나로 모든 팀원이 동일한 개발 환경을 구축할 수 있습니다. “내 컴퓨터에서는 되는데…”는 이제 과거의 유물이 됩니다. 💯
  • 쉬운 시작과 종료: 단 몇 개의 명령어(docker compose up, docker compose down)로 복잡한 다중 컨테이너 애플리케이션을 한 번에 시작하고 종료할 수 있습니다. 🚀
  • 격리된 환경: 각 서비스는 독립적인 컨테이너에서 실행되므로, 서로의 환경에 영향을 주지 않습니다. 🛡️
  • 버전 관리 가능: docker-compose.yml 파일은 코드와 함께 버전 관리 시스템(Git)에 포함되어 환경 설정을 코드처럼 관리할 수 있습니다. 📝

🧐 Docker Compose의 핵심 개념 파헤치기

Docker Compose는 docker-compose.yml이라는 YAML 파일 하나로 여러 개의 Docker 컨테이너를 정의하고 관리합니다. 이 파일의 주요 구성 요소를 살펴볼까요?

version: '3.8' # Docker Compose 파일 형식 버전

services: # 애플리케이션을 구성하는 서비스들 (컨테이너)
  web: # 'web'이라는 서비스 이름
    build: . # 현재 디렉토리의 Dockerfile을 사용해 이미지를 빌드
    ports:
      - "8000:8000" # 호스트 포트 8000을 컨테이너 포트 8000에 연결
    volumes:
      - ./app:/app # 호스트의 './app' 디렉토리를 컨테이너의 '/app' 디렉토리에 마운트 (코드 동기화)
    environment: # 컨테이너 내부에 설정할 환경 변수
      DATABASE_URL: postgres://user:password@db:5432/mydb
    depends_on: # 이 서비스가 시작되기 전에 'db' 서비스가 먼저 시작되어야 함
      - db

  db: # 'db'라는 서비스 이름
    image: postgres:13 # Docker Hub에서 'postgres:13' 이미지를 다운로드
    volumes:
      - db_data:/var/lib/postgresql/data # 데이터 영속성을 위한 볼륨 마운트
    environment: # 데이터베이스 설정 환경 변수
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password

volumes: # 이름 있는 볼륨 정의 (데이터 영속성 유지)
  db_data:

위 예시에서 볼 수 있듯이, docker-compose.yml 파일은 다음과 같은 핵심 개념으로 구성됩니다:

  1. version: Docker Compose 파일의 형식 버전을 지정합니다. 최신 버전을 사용하는 것이 좋습니다 (현재는 주로 ‘3.8’ 이상).
  2. services: 애플리케이션을 구성하는 각각의 컨테이너를 정의합니다. 각 서비스는 독립적인 기능을 수행합니다. (예: web 서비스는 웹 애플리케이션, db 서비스는 데이터베이스).
    • build: 현재 디렉토리에 있는 Dockerfile을 사용하여 사용자 정의 이미지를 빌드합니다. (예: build: .)
    • image: Docker Hub와 같은 컨테이너 레지스트리에서 미리 만들어진 이미지를 다운로드하여 사용합니다. (예: image: postgres:13)
    • ports: 호스트 머신의 포트와 컨테이너 내부의 포트를 연결하여 외부에서 컨테이너 서비스에 접근할 수 있게 합니다. (예: "8000:8000")
    • volumes: 호스트 머신의 특정 경로를 컨테이너 내부의 경로에 연결합니다. 주로 코드 동기화(./app:/app)나 데이터 영속성(db_data:/var/lib/postgresql/data)을 위해 사용됩니다.
    • environment: 컨테이너 내부에서 사용할 환경 변수를 설정합니다. 데이터베이스 연결 정보, API 키 등에 유용합니다.
    • depends_on: 서비스 간의 의존성을 정의합니다. 예를 들어, web 서비스가 db 서비스보다 먼저 시작되어야 할 때 사용합니다. (주의: depends_on은 시작 순서만 보장하며, 서비스가 완전히 준비되었음을 보장하지는 않습니다. healthcheck와 함께 사용하면 좋습니다.)
    • networks: 서비스들이 통신할 수 있는 가상 네트워크를 정의하고 연결합니다. (명시하지 않으면 기본 네트워크가 생성됩니다.)
  3. volumes: 컨테이너가 삭제되어도 데이터가 사라지지 않도록 영속성을 제공하는 “이름 있는 볼륨(Named Volumes)”을 정의합니다. 데이터베이스 데이터 등에 필수적입니다.

🚀 실전 예제: 웹 개발 환경 초고속 구축하기 (Flask + PostgreSQL)

이제 실제로 Docker Compose를 사용하여 간단한 Python Flask 웹 애플리케이션과 PostgreSQL 데이터베이스로 구성된 개발 환경을 구축해 봅시다!

🛠️ 1단계: 준비물 확인

  • Docker Desktop: macOS, Windows, Linux에 설치되어 있어야 합니다. Docker Compose는 Docker Desktop에 포함되어 있습니다.

📂 2단계: 프로젝트 구조 생성

새로운 프로젝트 디렉토리를 만들고 다음과 같이 구성합니다.

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

✍️ 3단계: app/app.py 작성 (Flask 애플리케이션)

간단한 Flask 애플리케이션 코드를 작성하여 데이터베이스 연결을 시도해 봅니다.

# app/app.py
import os
from flask import Flask
from sqlalchemy import create_engine, text

app = Flask(__name__)

# 환경 변수에서 데이터베이스 URL 가져오기
DATABASE_URL = os.environ.get('DATABASE_URL', 'postgresql://user:password@localhost:5432/mydb')
engine = create_engine(DATABASE_URL)

@app.route('/')
def hello_world():
    return 'Hello, Docker Compose World! 🌍'

@app.route('/db_test')
def db_test():
    try:
        with engine.connect() as connection:
            result = connection.execute(text("SELECT 1")).scalar()
            return f"Database connection successful! Result: {result} 🎉"
    except Exception as e:
        return f"Database connection failed: {e} 😭"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

📦 4단계: app/requirements.txt 작성

Flask와 SQLAlchemy 라이브러리를 정의합니다.

# app/requirements.txt
Flask==2.3.3
SQLAlchemy==2.0.23
psycopg2-binary==2.9.9 # PostgreSQL 드라이버

🐳 5단계: Dockerfile 작성 (Flask 앱 이미지 빌드용)

Flask 애플리케이션을 위한 Docker 이미지를 정의합니다.

# Dockerfile
# Python 공식 이미지 사용
FROM python:3.9-slim-buster

# 작업 디렉토리 설정
WORKDIR /app

# requirements.txt 복사 및 의존성 설치
COPY ./app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 애플리케이션 코드 복사
COPY ./app .

# Flask 애플리케이션 실행 명령어
CMD ["python", "app.py"]

📝 6단계: docker-compose.yml 작성 (핵심!)

이제 Flask 앱과 PostgreSQL 데이터베이스를 함께 구동할 docker-compose.yml 파일을 작성합니다.

# docker-compose.yml
version: '3.8'

services:
  web:
    build: . # 현재 디렉토리의 Dockerfile을 사용하여 이미지 빌드
    ports:
      - "5000:5000" # 호스트의 5000번 포트를 컨테이너의 5000번 포트에 연결
    volumes:
      - ./app:/app # 로컬 app 디렉토리를 컨테이너의 /app 디렉토리에 마운트 (코드 수정 시 실시간 반영)
    environment: # Flask 앱이 DB에 연결할 수 있도록 환경 변수 설정
      DATABASE_URL: postgresql://user:password@db:5432/mydb # 'db'는 Compose가 제공하는 서비스 이름
    depends_on:
      - db # web 서비스는 db 서비스가 시작된 후에 시작

  db:
    image: postgres:13 # PostgreSQL 13 버전 이미지 사용
    volumes:
      - db_data:/var/lib/postgresql/data # DB 데이터 영속성을 위한 이름 있는 볼륨 마운트
    environment: # PostgreSQL 환경 변수 설정
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432" # (선택사항) 호스트에서 DB에 직접 접근하고 싶을 때 주석 해제

volumes:
  db_data: # db 서비스에서 사용할 이름 있는 볼륨 정의
  • web 서비스: Dockerfile을 이용해 Flask 앱 이미지를 빌드하고, 로컬 코드(app/)를 컨테이너 내부(/app)에 마운트하여 코드 변경 시 컨테이너 재빌드 없이 바로 반영되도록 합니다. (app.py의 Flask debug=True 설정과 함께 개발 시 유용합니다.)
  • db 서비스: 공식 postgres:13 이미지를 사용합니다. db_data 볼륨을 사용해 데이터가 컨테이너가 삭제되어도 사라지지 않도록 합니다.

▶️ 7단계: Docker Compose 실행!

my-flask-app 디렉토리(즉, docker-compose.yml 파일이 있는 곳)에서 터미널을 열고 다음 명령어를 실행합니다:

docker compose up
  • 이 명령어는 docker-compose.yml 파일을 읽어 각 서비스를 정의된 순서대로 시작합니다.
  • web 서비스의 이미지가 없으면 Dockerfile을 사용하여 빌드하고, db 서비스의 이미지가 없으면 Docker Hub에서 postgres:13 이미지를 다운로드합니다.
  • 모든 서비스가 백그라운드에서 실행되도록 하려면 -d 옵션을 추가합니다: docker compose up -d

서비스가 성공적으로 시작되면 다음과 유사한 메시지를 볼 수 있습니다:

[+] Running 2/2
 ✔ Network my-flask-app_default  Created                                                                                     0.0s
 ✔ Container my-flask-app-db-1   Started                                                                                     0.0s
 ✔ Container my-flask-app-web-1  Started                                                                                     0.0s

🌐 8단계: 애플리케이션 접속 및 테스트

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

  • Flask 앱: http://localhost:5000
    • “Hello, Docker Compose World! 🌍” 메시지를 확인할 수 있습니다.
  • DB 연결 테스트: http://localhost:5000/db_test
    • “Database connection successful! Result: 1 🎉” 메시지를 확인했다면, Flask 앱이 PostgreSQL 데이터베이스와 성공적으로 연결된 것입니다!

🛑 9단계: 서비스 중지 및 삭제

모든 서비스를 중지하고 컨테이너를 삭제하려면 다음 명령어를 사용합니다:

docker compose down
  • 이 명령어는 docker-compose.yml에 정의된 모든 컨테이너와 네트워크를 중지하고 제거합니다.
  • volumes에 정의된 데이터(예: db_data)는 기본적으로 제거되지 않습니다. 볼륨까지 모두 제거하려면 -v 옵션을 추가합니다: docker compose down -v

✨ 자주 사용하는 Docker Compose 명령어

  • docker compose up: 모든 서비스를 시작합니다. (-d 옵션으로 백그라운드 실행)
  • docker compose down: 모든 서비스를 중지하고 제거합니다. (-v 옵션으로 볼륨까지 제거)
  • docker compose ps: 현재 실행 중인 서비스 목록과 상태를 보여줍니다.
  • docker compose logs [서비스_이름]: 특정 서비스의 로그를 확인합니다. (예: docker compose logs web)
  • docker compose exec [서비스_이름] [명령어]: 특정 서비스 컨테이너 내에서 명령어를 실행합니다. (예: docker compose exec web bash로 웹 컨테이너에 접속)
  • docker compose build [서비스_이름]: 특정 서비스의 이미지를 다시 빌드합니다. (예: docker compose build web)

✨ Docker Compose를 더 강력하게 활용하는 팁

🚀 1. docker compose watch로 초고속 개발 경험! (Docker Desktop 4.19+ 필요)

최신 Docker Desktop 버전(4.19 이상)에서는 docker compose watch 명령어가 추가되었습니다. 이 명령어는 docker-compose.yml에 설정된 volumes 변경을 감지하여, 로컬 파일이 수정될 때마다 자동으로 컨테이너 내의 파일을 동기화하거나 필요한 경우 컨테이너를 재시작해 줍니다. 개발 워크플로우를 혁신적으로 개선할 수 있습니다!

사용 예시:

docker-compose.yml 파일에 develop 섹션을 추가하여 watch 동작을 정의할 수 있습니다.

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - ./app:/app
    environment:
      DATABASE_URL: postgresql://user:password@db:5432/mydb
    depends_on:
      - db
    # watch 설정을 추가합니다.
    develop:
      watch:
        - action: sync+restart # 파일 변경 시 동기화 후 컨테이너 재시작
          path: ./app # 감시할 경로
          target: /app # 컨테이너 내 동기화될 경로
          ignore:
            - node_modules/ # 무시할 파일/폴더

  db:
    image: postgres:13
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password

volumes:
  db_data:

이제 터미널에서 docker compose watch를 실행하면, ./app 디렉토리의 파일이 변경될 때마다 web 컨테이너가 자동으로 재시작되어 변경 사항을 바로 확인할 수 있습니다. 마치 nodemon이나 webpack-dev-server처럼 동작합니다! 🤩

📄 2. 여러 개의 Compose 파일 사용하기 (-f 옵션)

개발, 테스트, 배포 환경마다 설정이 달라야 할 때 유용합니다.

  • docker-compose.yml: 기본 설정
  • docker-compose.dev.yml: 개발 환경 전용 오버라이드
  • docker-compose.prod.yml: 프로덕션 환경 전용 오버라이드
# 개발 환경 실행
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# 프로덕션 환경 실행
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

-f 옵션을 여러 번 사용하여 파일을 병합할 수 있습니다. 나중에 오는 파일이 이전 파일의 설정을 덮어씁니다.

🔐 3. 환경 변수 파일(.env)로 민감 정보 관리

데이터베이스 비밀번호 같은 민감한 정보는 docker-compose.yml에 직접 쓰는 대신 .env 파일에 분리하여 관리하는 것이 좋습니다.

# .env 파일 (docker-compose.yml과 같은 디렉토리에 위치)
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb

docker-compose.yml에서는 environment 섹션에서 .env 파일의 변수를 직접 참조할 수 있습니다.

# docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: ${POSTGRES_DB} # .env 파일에서 POSTGRES_DB 값을 가져옴
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

🩺 4. healthcheck로 서비스 준비 상태 확인

depends_on은 서비스 시작 순서만 보장할 뿐, 서비스가 실제로 “준비”되었는지는 보장하지 않습니다. 예를 들어, 데이터베이스 컨테이너가 시작되자마자 웹 서비스가 연결을 시도하면 DB가 아직 초기화되지 않아 실패할 수 있습니다. healthcheck를 사용하면 서비스가 실제로 요청을 처리할 준비가 되었는지 확인할 수 있습니다.

# docker-compose.yml
version: '3.8'

services:
  web:
    # ... (생략)
    depends_on:
      db:
        condition: service_healthy # db 서비스가 healthcheck를 통과해야 web 서비스 시작

  db:
    image: postgres:13
    # ... (생략)
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"] # DB 준비 상태 확인 명령어
      interval: 5s # 5초마다 체크
      timeout: 5s # 체크 타임아웃 5초
      retries: 5 # 5번 실패 시 unhealthy로 간주

마무리하며 🌟

이처럼 Docker Compose는 복잡한 로컬 개발 환경을 초고속으로, 일관성 있게, 그리고 재현 가능하게 구축할 수 있도록 돕는 강력한 도구입니다. 이제 더 이상 개발 환경 설정에 시간을 낭비하지 않고, 오직 코드 작성에만 집중할 수 있게 될 것입니다!

오늘 배운 개념과 예시들을 바탕으로 여러분의 다음 프로젝트에 Docker Compose를 적용해 보세요. 분명 개발 생산성이 크게 향상되는 것을 경험하실 수 있을 겁니다.

궁금한 점이 있다면 언제든 질문해 주세요! 즐거운 개발 되세요! 😊

답글 남기기

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