"도커 이미지만으로는 부족하다" - Docker Compose로 멀티 컨테이너 다루기

댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!

댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
Dockerfile을 작성하고, 이미지를 빌드하고, 컨테이너를 띄웠다. 여기까지는 잘 된다. 그런데 실제 서비스는 앱 하나로 돌아가지 않는다. 웹 서버 뒤에는 데이터베이스가 있고, 캐시가 있고, 때로는 메시지 큐도 있다.
이걸 전부 docker run으로 하나씩 띄우면 어떻게 될까? 컨테이너마다 네트워크를 연결하고, 포트를 매핑하고, 실행 순서를 맞추는 걸 매번 수동으로 해야 한다. 팀원이 "로컬에서 어떻게 띄워요?"라고 물으면, 명령어 다섯 줄을 순서대로 복붙하라고 알려줘야 한다.
Docker Compose는 이 문제를 해결한다. 여러 컨테이너의 구성을 하나의 파일로 정의하고, 명령어 한 줄로 전부 띄울 수 있다.
컨테이너 하나를 띄울 때는 docker run이면 충분하다.
docker run -d -p 3000:3000 --name my-app my-app:latest
그런데 여기에 PostgreSQL과 Redis를 추가해야 한다면?

docker network create my-network
docker run -d --name postgres \
--network my-network \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=mydb \
-v pgdata:/var/lib/postgresql/data \
postgres:16
docker run -d --name redis \
--network my-network \
redis:7-alpine
docker run -d --name my-app \
--network my-network \
-p 3000:3000 \
-e DATABASE_URL=postgresql://admin:secret@postgres:5432/mydb \
-e REDIS_URL=redis://redis:6379 \
my-app:latest
네트워크를 만들고, 환경 변수를 넘기고, 볼륨을 붙이고, 순서대로 실행해야 한다. 명령어가 길어지고, 실수하기 쉽고, 기억에 의존하게 된다. 컨테이너를 내릴 때도 하나씩 docker stop과 docker rm을 해야 한다.
Docker Compose는 멀티 컨테이너 애플리케이션을 compose.yaml 파일 하나로 정의하고 관리하는 도구다. 위에서 길게 나열한 docker run 명령어들을 YAML로 선언하면 된다.

services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://admin:secret@postgres:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
- postgres
- redis
postgres:
image: postgres:16
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
이 파일 하나에 앱, 데이터베이스, 캐시의 설정이 전부 담겨 있다. 네트워크는 Compose가 자동으로 생성해주기 때문에 별도로 만들 필요가 없다.
docker compose up -d
이 한 줄로 세 개의 컨테이너가 전부 뜬다. 내릴 때도 마찬가지다.

docker compose down
컨테이너, 네트워크까지 한 번에 정리된다. "어떤 순서로 띄워야 하지?" "네트워크 이름이 뭐였지?"를 더 이상 고민하지 않아도 된다.
Compose 파일의 핵심은 services다. 각 서비스가 하나의 컨테이너에 대응한다.
services:
app:
build: . # Dockerfile로 빌드
ports:
- "3000:3000" # 호스트:컨테이너 포트 매핑
postgres:
image: postgres:16 # 기존 이미지 사용
ports:
- "5432:5432"
build를 쓰면 로컬 Dockerfile로 이미지를 빌드하고, image를 쓰면 레지스트리에서 기존 이미지를 가져온다.
컨테이너에 전달할 환경 변수를 정의한다.
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
민감한 값은 YAML에 직접 쓰지 않고, .env 파일로 분리할 수도 있다.
services:
app:
build: .
env_file:
- .env
컨테이너가 삭제되면 내부 데이터도 사라진다. 데이터를 유지하려면 볼륨을 사용해야 한다.
services:
postgres:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
pgdata라는 이름의 볼륨을 만들고 PostgreSQL의 데이터 디렉토리에 연결했다. docker compose down을 해도 볼륨은 남아있다. 볼륨까지 지우고 싶으면 docker compose down -v를 쓴다.
서비스 간의 시작 순서를 지정한다.
services:
app:
build: .
depends_on:
- postgres
- redis
depends_on을 쓰면 postgres와 redis가 먼저 시작된 뒤에 app이 시작된다. 다만 주의할 점이 있다. "시작됐다"와 "준비됐다"는 다르다. PostgreSQL 컨테이너가 뜨는 것과 실제로 쿼리를 받을 준비가 되는 것은 별개다.
준비 상태까지 확인하려면 condition을 사용한다.
services:
app:
build: .
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 5s
retries: 5
healthcheck로 PostgreSQL이 실제로 연결을 받을 수 있는지 확인하고, 그 조건이 통과한 뒤에 app을 시작한다.
| 명령어 | 역할 |
|---|---|
docker compose up -d | 모든 서비스를 백그라운드로 시작 |
docker compose down | 모든 서비스 중지 및 정리 |
docker compose ps | 실행 중인 서비스 목록 확인 |
docker compose logs -f app | 특정 서비스의 로그 실시간 확인 |
docker compose build | 이미지 다시 빌드 |
docker compose restart app | 특정 서비스만 재시작 |
-d는 detached 모드(백그라운드 실행)를 뜻한다. 개발 중에 로그를 바로 보고 싶으면 -d 없이 실행하면 된다.
Docker Compose가 가장 빛나는 순간은 팀의 개발 환경을 통일할 때다.
팀원 A는 PostgreSQL 14를 쓰고, B는 16을 쓰고, C는 로컬에 직접 설치하지 않고 클라우드 DB를 쓴다. 스키마 호환성 문제가 생겨도 원인을 찾기 어렵다.
compose.yaml을 프로젝트 저장소에 커밋하면, 모든 팀원이 동일한 버전의 데이터베이스, 캐시, 메시지 큐를 로컬에서 쓸 수 있다. 새로운 팀원이 합류해도 세팅 가이드가 간단해진다.
1. 저장소를 클론한다
2. .env.example을 복사해서 .env를 만든다
3. docker compose up -d
4. npm install && npm run dev
"PostgreSQL 설치하고, 버전 맞추고, 유저 만들고, DB 만들고…" 같은 과정이 사라진다. 환경 설정에 쓰는 시간이 줄면 개발에 집중하는 시간이 늘어난다.
Docker Compose는 로컬 개발과 테스트에서 강력하지만, 프로덕션 운영 도구는 아니다. 단일 서버에서 간단한 서비스를 돌리는 데는 쓸 수 있지만, 여러 서버에 걸쳐 컨테이너를 분산시키거나 자동 스케일링이 필요하면 쿠버네티스 같은 오케스트레이션 도구를 써야 한다.
| 구분 | Docker Compose | Kubernetes |
|---|---|---|
| 용도 | 로컬 개발, 단일 서버 | 멀티 서버 프로덕션 운영 |
| 설정 파일 | compose.yaml | Deployment, Service YAML |
| 스케일링 | 수동 (replicas 지정) | 자동 (HPA 등) |
| 장애 복구 | 제한적 | 자가 치유 |
| 학습 곡선 | 낮음 | 높음 |
둘은 경쟁이 아니라 역할이 다른 도구다. 개발 환경은 Compose로, 프로덕션은 Kubernetes로 — 이 조합이 가장 흔하다.
도커 이미지 하나를 잘 만드는 것도 중요하지만, 실제 서비스는 여러 컨테이너가 함께 동작한다. Docker Compose는 이 멀티 컨테이너 구성을 파일 하나로 정의하고 명령어 한 줄로 관리할 수 있게 해준다.
지금 작업 중인 프로젝트에 compose.yaml이 없다면, 하나 만들어보자. 앱과 데이터베이스를 정의하고 docker compose up -d를 실행하는 것부터 시작하면 된다. "로컬 환경 어떻게 세팅해요?"라는 질문에 "compose up 하면 돼요"라고 답할 수 있는 순간, Compose의 가치를 실감할 수 있다.

도커로 컨테이너를 하나 띄우는 건 어렵지 않다. 한 줄이면 끝난다. 그런데 컨테이너가 10개, 100개로 늘어나면 얘기가 달라진다. 어느 서버에 올릴지, 몇 개를 띄울지, 하나가 죽으면 누가 다시 띄울지 — 직접 관리하기 시작하면 감당하기 어려워진다. 여기서 문제가 생긴다. 컨테이너는 만드는 것보다 운영하는 게 훨씬 어렵다. 서버가 죽어도 서비스는 살아 있

분명 내 로컬에서는 잘 돌아갔다. 그런데 서버에 올리니까 안 된다. 개발하다 보면 누구나 한 번쯤 겪는 상황이다. 원인은 단순하다. 개발 환경과 실행 환경이 다르기 때문이다. Node.js 버전, 운영체제, 설치된 패키지 — 어느 하나만 달라도 문제가 생길 수 있다. 도커는 이 환경 차이를 줄이기 위해 사용하는 도구다. 이 글에서는 도커의 기본 개념부터 이

AWS 콘솔에 접속해서 EC2를 하나 만들었다. 보안 그룹도 설정하고, S3 버킷도 만들었다. 잘 돌아간다. 그런데 같은 환경을 하나 더 만들어야 한다면? 콘솔을 열어서 똑같은 작업을 처음부터 다시 해야 한다. 여기서 문제가 시작된다. 클릭으로 만든 인프라는 기록이 남지 않는다. 무엇을 어떤 순서로 설정했는지 기억에 의존해야 하고, 사람이 반복하면 실수가