Docker Compose 완벽 가이드 | 멀티 컨테이너·네트워크·볼륨·환경 변수
이 글의 핵심
Docker Compose로 멀티 컨테이너 앱을 관리하는 완벽 가이드. 서비스 정의, 네트워크, 볼륨, 환경 변수, 프로덕션 설정까지 실전 예제로 정리. Docker·Docker Compose·Container 중심으로 설명합니다.
이 글의 핵심
Docker Compose로 멀티 컨테이너 앱을 관리하는 완벽 가이드입니다. 서비스 정의, 네트워크, 볼륨, 환경 변수, 프로덕션 설정뿐 아니라 오케스트레이션 라이프사이클, 네트워크·볼륨 심화, 변수 보간, 멀티 파일 병합·상속, 프로덕션 패턴까지 실전 예제로 정리했습니다.
실무 경험 공유: 개발 환경을 Docker Compose로 표준화하면서, 온보딩 시간을 3일에서 30분으로 단축하고 “내 컴퓨터에서는 되는데” 문제를 완전히 해결한 경험을 공유합니다.
들어가며: “개발 환경 설정이 복잡해요”
실무 문제 시나리오
시나리오 1: PostgreSQL, Redis 설치가 번거로워요
각각 설치하고 설정해야 합니다. Docker Compose는 한 번에 실행합니다. 시나리오 2: 팀원마다 환경이 달라요
버전, 설정이 다릅니다. Docker Compose로 일관성을 유지합니다. 시나리오 3: 여러 컨테이너를 수동으로 실행해요
docker run을 여러 번 실행합니다. Docker Compose는 한 명령으로 실행합니다.
1. Docker Compose란?
핵심 특징
Docker Compose는 멀티 컨테이너 Docker 앱을 정의하고 실행하는 도구입니다. 주요 장점:
- 간단한 설정: YAML 파일 하나
- 한 번에 실행: docker compose up
- 네트워크 자동 생성: 컨테이너 간 통신
- 볼륨 관리: 데이터 영속성
- 환경 변수: .env 파일 지원
2. 설치
터미널에서 다음 명령어를 실행합니다.
# Docker Desktop (Windows, macOS)
# Docker Compose 포함됨
# Linux
sudo apt install docker-compose-plugin
# 확인
docker compose version
3. 기본 사용
docker-compose.yml
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:
명령어
# 시작
docker compose up
# 백그라운드 실행
docker compose up -d
# 중지
docker compose down
# 로그 확인
docker compose logs
# 특정 서비스 로그
docker compose logs web
# 재시작
docker compose restart
4. 실전 예제: 풀스택 앱
설정 파일 예시입니다.
# docker-compose.yml
version: '3.8'
services:
# Frontend (React)
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:8000
depends_on:
- backend
# Backend (FastAPI)
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./backend:/app
# Database (PostgreSQL)
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
# Cache (Redis)
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
# Nginx (Reverse Proxy)
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- frontend
- backend
volumes:
postgres-data:
redis-data:
networks:
default:
name: myapp-network
5. 환경 변수
.env 파일
# .env
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secret
POSTGRES_DB=mydb
REDIS_PASSWORD=redis-secret
API_PORT=8000
FRONTEND_PORT=3000
사용
# docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
6. 네트워크
커스텀 네트워크
services:
frontend:
networks:
- frontend-network
backend:
networks:
- frontend-network
- backend-network
db:
networks:
- backend-network
networks:
frontend-network:
backend-network:
7. 헬스체크
설정 파일 예시입니다.
services:
backend:
image: myapp/backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
8. 프로덕션 설정
docker-compose.prod.yml
version: '3.8'
services:
backend:
image: myapp/backend:1.0.0
restart: always
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
db:
image: postgres:16
restart: always
volumes:
- /data/postgres:/var/lib/postgresql/data
# 프로덕션 실행
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
취업·면접과 연결하기
로컬·스테이징 환경을 Compose로 표준화한 경험은 온보딩·협업 스토리로 잘 맞습니다. 개발 취업 실전 팁의 포트폴리오·협업 절과, 실무형 과제 대비는 코딩 테스트 완벽 대비 가이드를 함께 보세요.
9. 심화: 서비스 오케스트레이션 라이프사이클
docker compose up 한 번이 내부에서 하는 일을 단계로 나누면, 장애 분석·재현·최적화가 훨씬 쉬워집니다. Compose V2(플러그인)는 Compose 규격 파일을 읽어 프로젝트(project) 단위로 리소스를 묶습니다. 프로젝트 이름은 기본적으로 상위 디렉터리 이름이며, COMPOSE_PROJECT_NAME 또는 -p로 바꿀 수 있습니다. 컨테이너·네트워크·볼륨 이름에 접두사로 붙어 동일 호스트에서 여러 스택을 격리합니다.
9.1 구성 로딩부터 기동까지의 흐름
- 설정 병합·검증:
-f로 지정한 파일들을 순서대로 병합하고(후행 파일이 동일 키를 덮어씀),include가 있으면 지정된 파일을 끌어옵니다. 스키마 검증 후 보간(interpolation) 이 적용됩니다. - 이미지 확보:
pull_policy(예:missing,always)와 로컬 이미지 유무에 따라 레지스트리에서 가져오거나 빌드합니다.build가 있으면 Dockerfile 컨텍스트를 빌드해 태그를 만듭니다. - 네트워크·볼륨 생성: 선언된 네트워크·볼륨이 없으면 생성합니다. 기본 네트워크는 프로젝트 스코프의 브리지 네트워크입니다.
- 컨테이너 생성·연결: 각 서비스의 컨테이너를 만들고 DNS 이름(서비스명)으로 서로를 해석할 수 있게 붙입니다.
- 시작 순서와 의존성:
depends_on은 컨테이너 기동 순서만 보장합니다. DB가 “준비됐는지”는 기본적으로 보장하지 않으므로, 애플리케이션 쪽 재시도·헬스체크·엔트리포인트 대기 스크립트가 필요합니다. Compose 규격에서는depends_on에condition: service_healthy등을 두어 헬스체크 통과 후 기동하도록 할 수 있습니다(이미지·설정에 헬스체크 정의가 있어야 함). - 실행 중: 재시작 정책(
restart), 시그널, 로그 드라이버가 적용됩니다.docker compose down은 기본적으로 네트워크를 제거하고, 볼륨은-v를 줄 때만 삭제합니다.
9.2 depends_on의 함정과 해법
백엔드가 DB보다 먼저 요청을 보내 연결 실패가 나는 경우가 흔합니다. 운영에서는 (1) 백엔드 코드에서 연결 재시도, (2) healthcheck + depends_on 조건, (3) 초기화 전용 init 컨테이너 또는 엔트리포인트에서 pg_isready 등으로 대기 —를 조합합니다. 개발 편의만으로 depends_on만 믿으면 레이스가 남습니다.
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 5s
timeout: 3s
retries: 10
api:
depends_on:
db:
condition: service_healthy
10. 심화: 네트워크·볼륨 관리
10.1 네트워크: 기본 브리지와 분리
같은 Compose 프로젝트의 서비스는 기본적으로 한 브리지 네트워크에 붙으며, 서비스 이름이 DNS 이름이 됩니다. frontend에서 http://backend:8000처럼 호출하는 패턴이 이 때문입니다. networks로 여러 네트워크를 정의하면 서비스별로 어느 세그먼트에 붙일지 나눌 수 있어, DB는 백엔드 전용 네트워크에만 두는 식의 최소 노출이 가능합니다.
networks:
edge:
app:
internal: true # 외부 라우팅 차단(동일 브리지 내 통신만)
services:
proxy:
networks: [edge]
api:
networks: [edge, app]
db:
networks: [app]
internal: true는 해당 네트워크를 인터넷/호스트와 격리하는 용도로 쓰입니다(드라이버·환경에 따라 동작을 항상 확인하세요).
별칭(alias) 으로 레거시 호스트명을 흡수할 수 있습니다.
services:
api:
networks:
app:
aliases:
- legacy-api
10.2 볼륨: 이름 붙은 볼륨 vs 바인드 마운트
- 이름 붙은 볼륨(
volumes:아래 선언): Docker가 저장 위치를 관리합니다. 백업은docker run --volumes-from또는 볼륨을 마운트한 임시 컨테이너로 복사하는 방식이 일반적입니다. - 바인드 마운트(
./src:/app): 호스트 경로를 그대로 공유합니다. 개발 시 핫 리로드에 좋지만, 경로·권한·OS 차이(Windows/macOS 볼륨 성능) 이슈가 생깁니다. - 익명/임시 볼륨: 디버깅·캐시에 쓰이기도 하며, 스택 제거 시 정리 정책을 반드시 확인해야 합니다.
프로덕션 DB 데이터는 이름 붙은 볼륨 또는 명시적 호스트 경로로 두고, 백업·복구 절차를 문서화하는 것이 안전합니다. down -v는 이름 붙은 볼륨까지 삭제할 수 있으므로 운영 환경에서 주의합니다.
volumes:
pgdata:
driver: local
services:
db:
volumes:
- pgdata:/var/lib/postgresql/data
11. 심화: 환경 변수 보간(Interpolation)
Compose 파일의 ${VAR} 형태는 클라이언트 측에서 먼저 치환됩니다. 컨테이너 안의 쉘이 해석하는 것과 단계가 다릅니다.
11.1 문법과 이스케이프
${VAR}: 셸 또는.env에서 값을 가져옵니다.${VAR:-default}: 없으면 기본값.${VAR:?err}: 없으면 오류로 실패(필수 변수에 유용).$$: Compose 처리 후 컨테이너에$하나로 전달(예:$$HOME).
11.2 .env 파일의 위치와 우선순위
- 프로젝트 디렉터리의
.env는 보간에 자주 사용됩니다.--env-file로 다른 파일을 지정할 수도 있습니다. - 서비스의
environment:와env_file:은 컨테이너 환경을 구성하며, YAML 보간 단계와는 별개입니다. “Compose가 읽는 변수”와 “앱이 받는 변수”를 혼동하지 않는 것이 중요합니다.
services:
api:
environment:
DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
env_file:
- .env.local
비밀은 Git에 넣지 말고, CI/CD·시크릿 스토어에서 .env를 주입하거나 런타임에만 마운트하세요.
12. 멀티 스테이지·멀티 파일: Compose 상속과 병합
12.1 여러 -f 파일 병합
가장 흔한 패턴은 베이스 + 오버레이입니다. 뒤에 온 파일이 앞의 값을 덮어씁니다.
docker compose -f docker-compose.yml -f docker-compose.override.yml up
docker-compose.override.yml은 같은 디렉터리에 두면 일부 버전에서 자동 로드되던 관행이 있었으나, 명시적 -f로 관리하는 편이 재현성이 좋습니다.
12.2 include (Compose 규격)
공통 조각을 여러 스택에서 재사용하려면 Compose 파일의 include 로 다른 파일을 끌어올 수 있습니다(환경·경로 기준은 사용 중인 Compose 버전 문서를 확인하세요).
include:
- path: ./compose/infra.yml
팀 규모가 커질수록 공통 네트워크·로깅·레이블을 include로 모아 중복을 줄입니다.
12.3 COMPOSE_FILE 환경 변수
여러 파일을 한 번에 지정하기 어려울 때 COMPOSE_FILE에 경로를 나열해 기본 병합 세트를 팀에 공유할 수 있습니다. Unix 계열은 :로, Windows는 ;로 구분하는 것이 일반적입니다(환경·Compose 버전에 따라 다를 수 있으니 공식 문서를 확인하세요).
12.4 extends에 대해
과거 Compose 파일 형식의 extends 는 재사용을 위해 쓰였으나, 최신 Compose 규격에서는 include와 다중 파일 병합으로 대체하는 추세입니다. 레거시 프로젝트를 유지보수할 때만 확인하면 됩니다.
13. 프로덕션 Docker Compose 패턴
단일 호스트·소규모 배포에서 Compose는 여전히 유효합니다. 다만 Swarm/Kubernetes와 달리 스케줄링·오토스케일·롤링 업데이트가 플랫폼 차원에서 제공되지 않으므로, 프로세스 감독·배포 절차·백업을 외부(시스템d, 모니터링, 스크립트)로 보완합니다.
13.1 리소스 제한과 deploy 키 주의
deploy: 아래 resources 등은 Docker Swarm의 docker stack deploy 에서 의미가 분명한 항목이 많습니다. 일반 docker compose up만 쓰는 경우에는 무시되거나 기대와 다르게 동작할 수 있어, 단일 호스트 제한은 서비스 수준의 mem_limit, cpus 등(엔진·Compose 버전에 맞는 키)과 호스트 모니터링을 병행하는 편이 안전합니다. 실제로 어떤 키가 적용되는지는 docker compose config로 렌더링된 결과와 docker inspect로 검증하세요.
13.2 재시작·로깅·읽기 전용
services:
api:
image: myorg/api:1.2.3
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
user: "1000:1000"
read_only: true는 루트 파일시스템 쓰기를 막습니다. 앱이 /tmp 등에 쓰면 tmpfs 또는 볼륨을 추가합니다.
13.3 프로파일(profile)로 dev/prod 분리
profiles:로 기본 기동에서 제외할 수 있어, 로컬 전용 도구(메일 목업, Adminer)를 프로덕션에서 실수로 올리는 일을 줄입니다.
services:
adminer:
image: adminer
profiles: ["tools"]
docker compose --profile tools up -d
13.4 헬스체크·롤아웃
무중단에 가깝게 가려면 새 버전 컨테이너를 띄운 뒤 헬스체크 통과 후 트래픽을 전환하는 식의 블루그린/프록시 뒤 교체가 필요합니다. Compose 단독으로는 Kubernetes 수준의 롤링 업데이트가 없으므로, 리버스 프록시(Nginx, Traefik) 와 배포 스크립트를 조합하는 경우가 많습니다.
13.5 보안 요약
- 루트 컨테이너 지양, 최소 권한 사용자.
- 시크릿을 이미지에 넣지 않기; 런타임 주입과 로테이션.
- 불필요한 포트 노출 금지(가능하면 리버스 프록시 한 곳만 공개).
정리 및 체크리스트
핵심 요약
- Docker Compose: 멀티 컨테이너 앱 관리
- 간단한 설정: YAML 파일
- 라이프사이클: 병합·보간 → 이미지·네트워크·볼륨 → 컨테이너 생성·기동 →
depends_on은 순서만(준비 여부는 별도) - 네트워크: 프로젝트 브리지·서비스 DNS·
internal/별칭으로 세그먼트 분리 - 볼륨: 이름 붙은 볼륨 vs 바인드·
down -v주의 - 환경 변수: 보간(
${VAR:-})·.envvs 컨테이너env_file - 멀티 파일:
-f후행 우선·include·COMPOSE_FILE - 헬스체크: 재시작·
depends_on조건과 함께 쓸 때 의미가 큼 - 프로덕션:
restart, 로그 로테이션,read_only, 비루트, 프로파일,deploy는 Swarm 여부 확인
구현 체크리스트
- docker-compose.yml 작성
- 서비스 정의
- 네트워크 설정
- 볼륨 설정
- 환경 변수·보간 규칙 정리
- 헬스체크·준비 완료 대기 전략
- 멀티 파일 병합·공통
include여부 검토 - 프로덕션 설정(리소스·로그·보안·프로파일)
같이 보면 좋은 글
- Kubernetes 실전 가이드
- GitHub Actions CI/CD 가이드
- Terraform 실전 가이드
이 글에서 다루는 키워드
Docker, Docker Compose, Container, DevOps, Microservices, Infrastructure
자주 묻는 질문 (FAQ)
Q. Docker Compose vs Kubernetes, 어떤 게 나은가요?
A. Docker Compose는 단일 서버용입니다. Kubernetes는 클러스터용입니다. 소규모는 Docker Compose, 대규모는 Kubernetes를 권장합니다.
Q. 프로덕션에서 사용해도 되나요?
A. 소규모 앱은 괜찮지만, 대규모는 Kubernetes를 권장합니다.
Q. Swarm vs Compose, 차이가 뭔가요?
A. Swarm은 오케스트레이션 도구입니다. Compose는 개발 도구입니다. 프로덕션은 Swarm이나 Kubernetes를 사용하세요.
Q. 볼륨 데이터는 어디에 저장되나요?
A. Docker가 관리하는 디렉터리에 저장됩니다. docker volume inspect <volume_name>으로 확인할 수 있습니다.
Q. depends_on만으로 DB 준비를 보장할 수 있나요?
A. 아니요. 컨테이너 시작 순서만 맞춥니다. 앱에서 재시도하거나, healthcheck + depends_on 조건, 또는 준비 대기 스크립트를 쓰세요.
Q. deploy.resources를 넣었는데 docker compose up에서 제한이 안 걸리는 것 같아요.
A. deploy는 Swarm stack deploy 에 맞춘 항목이 많습니다. 단일 호스트 Compose에서는 docker compose config와 docker inspect로 실제 적용 여부를 확인하고, 필요하면 서비스 수준 제한 키·호스트 cgroup 모니터링을 병행하세요.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Docker Compose 완벽 가이드 | 멀티 컨테이너·네트워크·볼륨·환경 변수」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Docker Compose 완벽 가이드 | 멀티 컨테이너·네트워크·볼륨·환경 변수」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.