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 컨테이너로 각각 실행하려면, 다음과 같은 문제에 직면합니다.
- 길고 복잡한
docker run
명령: 각 컨테이너마다 포트 포워딩, 볼륨 마운트, 환경 변수 설정, 네트워크 연결 등 수많은 옵션을 일일이 지정해야 합니다. 😵 - 의존성 관리: 데이터베이스 컨테이너가 먼저 실행되어야 백엔드 서버가 제대로 시작될 수 있는데, 이 순서를 수동으로 맞춰주는 것도 일입니다.
- 네트워크 설정: 컨테이너 간 통신을 위해 별도의 네트워크를 생성하고 연결해야 합니다.
- 재현성 부족: 다른 팀원이나 새로운 환경에서 이 복잡한 설정을 그대로 재현하기 어렵습니다. “내 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가 자동으로 설치됩니다. 별도의 설치가 필요 없습니다! 👍
- Docker Desktop 공식 다운로드: https://www.docker.com/products/docker-desktop/
- 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_HOST
를 db
로 설정한 이유는 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를 적극적으로 활용해 보세요. 분명 놀라운 변화를 경험하실 겁니다!
궁금한 점이 있다면 언제든지 댓글로 질문해주세요! 다음에는 더 유익한 내용으로 찾아오겠습니다. 해피 코딩! 💻✨