Docker Compose 실전 가이드 | 멀티 컨테이너·네트워크·볼륨·프로덕션 배포
이 글의 핵심
Docker Compose로 멀티 컨테이너 애플리케이션을 구성하고 배포하는 완벽 가이드입니다. 네트워크, 볼륨, 환경 변수, 헬스체크, 프로덕션 배포까지 실무에 바로 적용할 수 있습니다.
실무 경험 공유: 스트리밍 인프라의 모니터링 스택(Prometheus, Grafana, Loki)을 Docker Compose로 구축하면서, 개발 환경 구성 시간을 2시간에서 5분으로 단축한 경험을 공유합니다.
들어가며: “컨테이너 여러 개를 어떻게 관리하죠?”
실무 문제 시나리오
시나리오 1: 개발 환경 구성이 복잡해요
웹 서버, 데이터베이스, Redis, Nginx를 각각 설치하고 설정하는 데 하루가 걸립니다. Docker Compose로 docker compose up 한 번에 모든 환경을 구성할 수 있습니다.
시나리오 2: 팀원마다 환경이 달라요
”내 컴퓨터에서는 되는데요?” 문제가 반복됩니다. Docker Compose로 모든 팀원이 동일한 환경을 공유할 수 있습니다.
시나리오 3: 프로덕션 배포가 불안해요
개발 환경과 프로덕션 환경이 달라 배포 후 에러가 발생합니다. Docker Compose로 환경을 일치시킬 수 있습니다.
flowchart TB
subgraph Before["문제 상황"]
A1[수동 설치]
A2[환경 불일치]
A3[배포 실패]
end
subgraph After["Docker Compose"]
B1[docker compose up]
B2[동일 환경]
B3[안정적 배포]
end
Before --> After
1. Docker Compose 기초
Docker Compose란?
여러 컨테이너를 정의하고 실행하는 도구입니다. YAML 파일로 서비스, 네트워크, 볼륨을 선언적으로 정의합니다.
설치
# Docker Desktop 설치 시 자동 포함
docker compose version
# Linux에서 별도 설치
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
최소 예제
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
# 실행
docker compose up
# 백그라운드 실행
docker compose up -d
# 중지 및 삭제
docker compose down
2. 멀티 컨테이너 애플리케이션
실전 예제: 웹 애플리케이션 스택
# docker-compose.yml
version: '3.8'
services:
# Nginx 웹 서버
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./public:/usr/share/nginx/html:ro
depends_on:
- app
networks:
- frontend
# Node.js 애플리케이션
app:
build:
context: ./app
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/mydb
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
# PostgreSQL 데이터베이스
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
# Redis 캐시
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- backend
volumes:
postgres_data:
redis_data:
networks:
frontend:
backend:
서비스 간 의존성
flowchart LR
subgraph Frontend["Frontend Network"]
Nginx
App
end
subgraph Backend["Backend Network"]
App2[App]
DB[PostgreSQL]
Redis
end
Nginx --> App
App --> App2
App2 --> DB
App2 --> Redis
3. 네트워크 구성
기본 네트워크
services:
web:
image: nginx
networks:
- frontend
app:
image: node:18
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
networks:
frontend:
backend:
커스텀 네트워크
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
backend:
driver: bridge
internal: true # 외부 접근 차단
외부 네트워크 연결
networks:
existing_network:
external: true
name: my-pre-existing-network
4. 볼륨 관리
Named Volumes
services:
db:
image: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
driver: local
Bind Mounts
services:
web:
image: nginx
volumes:
# 호스트 경로:컨테이너 경로:옵션
- ./nginx.conf:/etc/nginx/nginx.conf:ro # 읽기 전용
- ./public:/usr/share/nginx/html
tmpfs Mounts (메모리)
services:
app:
image: node:18
tmpfs:
- /tmp
- /run:size=100M,mode=1777
볼륨 백업 및 복원
# 백업
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz -C /data .
# 복원
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar xzf /backup/postgres_backup.tar.gz -C /data
5. 환경 변수 및 시크릿
.env 파일
# .env
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
POSTGRES_DB=myapp
NODE_ENV=production
API_KEY=your-api-key
# docker-compose.yml
services:
db:
image: postgres
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
환경 파일 분리
services:
app:
image: node:18
env_file:
- .env.common
- .env.production
시크릿 관리 (Docker Swarm)
services:
app:
image: node:18
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true
6. 헬스체크 및 재시작 정책
헬스체크
services:
app:
image: node:18
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
재시작 정책
services:
app:
image: node:18
restart: unless-stopped # 수동 중지 전까지 재시작
worker:
image: node:18
restart: on-failure:3 # 실패 시 최대 3회 재시작
cache:
image: redis
restart: always # 항상 재시작
7. 빌드 및 이미지 관리
Dockerfile과 통합
services:
app:
build:
context: ./app
dockerfile: Dockerfile
args:
- NODE_VERSION=18
- BUILD_ENV=production
target: production # 멀티스테이지 빌드의 특정 단계
cache_from:
- myapp:latest
# app/Dockerfile
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS development
RUN npm install
FROM base AS production
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
이미지 빌드 및 푸시
# 빌드
docker compose build
# 특정 서비스만 빌드
docker compose build app
# 빌드 후 실행
docker compose up --build
# 이미지 푸시
docker compose push
8. 프로덕션 배포
프로덕션 설정 분리
# docker-compose.yml (기본)
version: '3.8'
services:
app:
image: myapp:latest
environment:
- NODE_ENV=development
# docker-compose.prod.yml (프로덕션 오버라이드)
version: '3.8'
services:
app:
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# 프로덕션 실행
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
리소스 제한
services:
app:
image: node:18
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
로깅 설정
services:
app:
image: node:18
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
9. 실전 예제: 풀스택 애플리케이션
프로젝트 구조
myapp/
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
└── scripts/
├── init-db.sql
└── backup.sh
docker-compose.yml
version: '3.8'
services:
nginx:
build: ./nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- frontend
- backend
networks:
- frontend
restart: unless-stopped
frontend:
build:
context: ./frontend
target: production
environment:
- NEXT_PUBLIC_API_URL=http://backend:3000
networks:
- frontend
restart: unless-stopped
backend:
build:
context: ./backend
target: production
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
backup:
image: postgres:15-alpine
volumes:
- postgres_data:/data:ro
- ./backups:/backups
entrypoint: /bin/sh
command: -c "while true; do pg_dump -U user -h db myapp > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql; sleep 86400; done"
depends_on:
- db
networks:
- backend
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
frontend:
backend:
Nginx 설정
# nginx/nginx.conf
upstream frontend {
server frontend:3000;
}
upstream backend {
server backend:3000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
10. 자주 하는 실수와 해결법
문제 1: 컨테이너 간 통신 안 됨
원인: 같은 네트워크에 있지 않음.
# ❌ 잘못된 코드
services:
app:
image: node:18
# 네트워크 지정 안 함
db:
image: postgres
networks:
- backend
# ✅ 올바른 코드
services:
app:
image: node:18
networks:
- backend
db:
image: postgres
networks:
- backend
문제 2: 볼륨 데이터 손실
원인: docker compose down -v로 볼륨까지 삭제.
# ❌ 볼륨까지 삭제
docker compose down -v
# ✅ 컨테이너만 삭제
docker compose down
문제 3: 환경 변수 인식 안 됨
원인: .env 파일 위치 또는 형식 오류.
# .env 파일은 docker-compose.yml과 같은 디렉터리에
# 공백 없이 작성
DB_PASSWORD=secret123
# ❌ DB_PASSWORD = secret123 (공백 있으면 안 됨)
문제 4: 포트 충돌
원인: 호스트 포트가 이미 사용 중.
# 포트 사용 확인
sudo lsof -i :80
# 다른 포트로 변경
services:
nginx:
ports:
- "8080:80" # 호스트 8080 포트 사용
11. 모니터링 및 로깅
Docker Compose 상태 확인
# 실행 중인 서비스 확인
docker compose ps
# 로그 확인
docker compose logs
# 특정 서비스 로그
docker compose logs app
# 실시간 로그
docker compose logs -f
# 최근 100줄
docker compose logs --tail=100
리소스 사용량 모니터링
# 실시간 리소스 사용량
docker stats
# Compose 서비스만
docker compose ps -q | xargs docker stats
Prometheus + Grafana 통합
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
prometheus_data:
grafana_data:
12. CI/CD 통합
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
run: |
docker compose build
docker compose push
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /app
docker compose pull
docker compose up -d
정리 및 체크리스트
핵심 요약
- Docker Compose는 멀티 컨테이너 애플리케이션을 YAML로 정의하고 관리
- 네트워크로 서비스 간 통신을 격리하고 제어
- 볼륨으로 데이터를 영구 저장
- 헬스체크와 재시작 정책으로 안정성 확보
- 환경별 설정 분리로 개발/프로덕션 환경 관리
프로덕션 체크리스트
- 환경 변수를
.env파일로 분리 - 시크릿 정보는
.gitignore에 추가 - 헬스체크 설정
- 재시작 정책 설정
- 리소스 제한 설정
- 로깅 드라이버 설정
- 백업 전략 수립
- 모니터링 도구 연동
같이 보면 좋은 글
- Kubernetes 실전 가이드 | Pod·Service·Deployment·Ingress
- 웹 보안 완벽 가이드 | OWASP Top 10·XSS·CSRF·JWT
- Next.js 15 완벽 가이드 | App Router·Server Actions·Turbopack
이 글에서 다루는 키워드
Docker, Docker Compose, 컨테이너, DevOps, 멀티 컨테이너, 네트워크, 볼륨, 배포, CI/CD
자주 묻는 질문 (FAQ)
Q. Docker Compose vs Kubernetes, 언제 뭘 쓰나요?
A. 소규모 프로젝트, 개발 환경은 Docker Compose를 권장합니다. 대규모, 멀티 클러스터, 자동 스케일링이 필요하면 Kubernetes를 사용하세요.
Q. 프로덕션에서 Docker Compose를 써도 되나요?
A. 단일 서버 배포는 가능합니다. 하지만 고가용성, 자동 스케일링이 필요하면 Kubernetes나 Docker Swarm을 고려하세요.
Q. 볼륨 데이터를 백업하려면?
A. docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data . 명령으로 백업할 수 있습니다.
Q. 컨테이너 간 통신이 안 되는데요?
A. 같은 네트워크에 있는지 확인하세요. docker compose logs로 네트워크 오류를 확인할 수 있습니다.