G: 안녕하세요! 🐳 오늘은 컨테이너 기반 애플리케이션 개발의 생산성을 비약적으로 높여줄 도구, 바로 Docker Compose에 대해 완벽하게 파헤쳐 보는 시간을 갖겠습니다. 컨테이너 하나는 쉬워도, 여러 컨테이너가 복잡하게 얽힌 애플리케이션은 어떻게 관리해야 할지 막막하셨다면, 이 글이 여러분의 고민을 해결해 줄 마법 지팡이가 되어줄 거예요! 🪄
💡 Docker Compose는 왜 필요할까요? – 복잡한 컨테이너 오케스트레이션의 구원자 🚀
Docker는 개별 컨테이너를 쉽게 만들고 실행할 수 있게 해줍니다. 하지만 대부분의 실제 애플리케이션은 단순히 웹 서버 하나만으로 이루어져 있지 않습니다. 웹 서버, 데이터베이스, 캐시, 메시지 큐 등 여러 개의 서비스가 서로 유기적으로 연결되어 동작하죠.
상상해 보세요:
- Node.js 웹 서버 컨테이너 🌐
- PostgreSQL 데이터베이스 컨테이너 🗄️
- Redis 캐시 컨테이너 💾
이 세 가지 컨테이너를 수동으로 연결하고 관리하려면 어떻게 해야 할까요?
- 각 컨테이너를
docker run
명령어로 일일이 실행해야 합니다. - 컨테이너 간의 네트워크를 수동으로 설정해야 합니다. (예:
--link
옵션, 사용자 정의 네트워크) - 데이터베이스 연결 정보, 환경 변수 등을 각 컨테이너에 맞춰 설정해야 합니다.
- 이 모든 설정을 개발자마다, 환경마다 똑같이 반복해야 합니다. 🔄
생각만 해도 복잡하고 실수할 가능성이 많겠죠? 🤯 Docker Compose는 바로 이러한 불편함을 해결하기 위해 등장했습니다.
Docker Compose의 핵심 아이디어: 하나의 YAML 파일을 사용하여 다중 컨테이너 Docker 애플리케이션의 모든 서비스를 정의하고, 단일 명령어로 시작, 중지 및 빌드할 수 있게 해줍니다. 마치 오케스트라의 지휘자처럼, 여러 악기(컨테이너)들이 하나의 아름다운 멜로디(애플리케이션)를 연주할 수 있도록 조율해 주는 것이죠. 🎵
⚙️ Docker Compose 핵심 요소: docker-compose.yml
파헤치기
Docker Compose의 모든 마법은 docker-compose.yml
이라는 YAML 파일에서 시작됩니다. 이 파일은 애플리케이션을 구성하는 서비스, 네트워크, 볼륨 등을 정의합니다.
기본 구조부터 자세히 알아볼까요?
version: '3.8' # Docker Compose 파일 형식 버전 지정 (최신 버전을 사용하는 것이 좋습니다!)
services: # 애플리케이션을 구성하는 서비스들을 정의합니다.
서비스_이름_1:
image: 이미지_이름:태그 # 사용할 도커 이미지 지정 (Docker Hub에서 가져옴)
build: ./경로 # Dockerfile이 있는 경로 지정 (직접 이미지를 빌드할 경우)
ports: # 호스트 포트와 컨테이너 포트 매핑
- "호스트_포트:컨테이너_포트"
volumes: # 컨테이너와 호스트 간의 볼륨 매핑 (데이터 영속성, 코드 동기화)
- "호스트_경로:컨테이너_경로"
environment: # 컨테이너 내에서 사용할 환경 변수 설정
- KEY=VALUE
depends_on: # 서비스 간의 의존성 지정 (시작 순서 보장)
- 서비스_이름_2
networks: # 이 서비스가 사용할 네트워크 지정
- 내_네트워크_이름
# 기타 옵션들...
서비스_이름_2:
# ... (다른 서비스 정의)
networks: # 사용자 정의 네트워크를 정의합니다. (컨테이너 간 통신 격리/제어)
내_네트워크_이름:
driver: bridge # 네트워크 드라이버 지정 (기본값: bridge)
volumes: # 명명된 볼륨을 정의합니다. (데이터 영속성 보장)
내_볼륨_이름:
driver: local # 볼륨 드라이버 지정 (기본값: local)
각 섹션을 좀 더 자세히 살펴봅시다.
1. version
version: '3.8'
docker-compose.yml
파일의 문법 버전을 지정합니다. 버전에 따라 지원하는 기능이나 문법이 조금씩 다를 수 있으므로, 최신 버전을 사용하는 것이 좋습니다. (현재 주로 3.x 버전을 사용합니다.)
2. services
(가장 중요!)
이 섹션에 애플리케이션을 구성하는 각 컨테이너 서비스들을 정의합니다. 각 서비스는 독립적인 컨테이너로 실행됩니다.
image
vs.build
:image: postgres:14
: Docker Hub나 다른 레지스트리에 있는 이미지를 직접 가져와 사용할 때 지정합니다. (예:postgres
,nginx
,redis
등) 📥build: .
또는build: ./web_app
: 현재 디렉터리(.
)나 지정된 경로(web_app
)에 있는Dockerfile
을 사용하여 직접 이미지를 빌드할 때 사용합니다. 🛠️
ports
:- "80:8080"
:호스트_포트:컨테이너_포트
형식으로 포트를 매핑합니다. 호스트의 80번 포트로 들어오는 요청을 컨테이너의 8080번 포트로 전달하겠다는 의미입니다.
volumes
:- "./app:/usr/src/app"
:호스트_경로:컨테이너_경로
형식으로 볼륨을 매핑합니다. 호스트의app
디렉터리가 컨테이너의/usr/src/app
디렉터리에 연결됩니다.- 코드 동기화: 개발 중인 코드를 컨테이너에 실시간으로 반영하여 편리하게 개발할 수 있습니다. 💻
- 데이터 영속성: 데이터베이스 파일 등 컨테이너가 삭제되어도 사라지지 않아야 할 데이터를 호스트에 저장하여 영속성을 확보합니다. 💾
- "db_data:/var/lib/postgresql/data"
: 명명된 볼륨(named volume)을 사용할 수도 있습니다. 이는volumes
섹션에 정의된 볼륨 이름을 참조합니다.
environment
:- DB_HOST=db
- DB_PORT=5432
docker run -e
옵션과 동일하게 컨테이너 내부에서 사용할 환경 변수를 설정합니다. 주로 데이터베이스 연결 정보, API 키 등을 설정할 때 유용합니다.
depends_on
:- db
- 서비스 간의 의존성을 지정하여 시작 순서를 제어합니다. 예를 들어,
web
서비스가db
서비스에depends_on
으로 의존하면,db
서비스가 먼저 시작된 후에web
서비스가 시작됩니다. 하지만 이는 “시작 순서”만 보장하며,db
서비스가 “완전히 준비”될 때까지 기다리지는 않습니다. (이를 위해서는 건강 체크(health check)를 사용해야 합니다.)
networks
:- my_app_network
- 특정 서비스가 사용할 네트워크를 지정합니다. 지정하지 않으면 기본적으로
default
네트워크에 연결됩니다.
container_name
:container_name: my_web_app
- 컨테이너가 실행될 때 부여될 이름을 지정합니다. 지정하지 않으면 Docker Compose가 자동으로 이름을 생성합니다.
3. networks
사용자 정의 네트워크를 생성할 수 있습니다. 이는 컨테이너 간의 통신을 더 세밀하게 제어하거나, 특정 서비스 그룹을 격리할 때 유용합니다.
- 예:
backend_network
,frontend_network
4. volumes
명명된 볼륨(named volumes)을 정의합니다. docker volume create
명령으로 생성하는 볼륨과 동일합니다. 주로 데이터베이스 데이터처럼 영구적으로 보존되어야 하는 데이터를 저장하는 데 사용됩니다.
db_data:
: 이렇게 정의하면 Docker가 자동으로db_data
라는 이름의 볼륨을 생성하고 관리합니다.
🚀 자주 사용되는 Docker Compose 명령어
docker-compose.yml
파일을 작성했다면, 이제 몇 가지 간단한 명령어로 복잡한 애플리케이션을 제어할 수 있습니다. (참고: docker-compose
는 과거의 명령어이며, 요즘은 Docker Desktop 설치 시 기본으로 포함되는 docker compose
명령어를 사용하는 것이 권장됩니다. 하이픈이 없습니다!)
-
docker compose up
: 🚀docker-compose.yml
파일에 정의된 모든 서비스를 빌드하고 시작합니다.-d
옵션:--detach
의 약자로, 백그라운드에서 컨테이너를 실행하고 터미널을 해제합니다. (가장 많이 사용)--build
옵션: 이미지 빌드 강제 (코드 변경 시 유용)- 예시:
docker compose up -d --build
-
docker compose down
: 🛑up
명령어로 시작된 모든 서비스 컨테이너를 중지하고 제거합니다.-v
옵션:--volumes
의 약자로, 컨테이너와 함께 생성된 모든 볼륨도 함께 제거합니다. (주의: 데이터 유실 가능성!)- 예시:
docker compose down -v
-
docker compose ps
: 📊- 현재 실행 중인 모든 서비스 컨테이너의 상태를 확인합니다.
- 컨테이너 이름, 명령어, 상태, 포트 매핑 등을 보여줍니다.
-
docker compose logs [서비스_이름]
: 📜- 모든 서비스 또는 특정 서비스의 로그를 실시간으로 확인합니다.
-f
옵션:--follow
의 약자로, 로그를 계속해서 스트리밍합니다.- 예시:
docker compose logs -f web
-
docker compose exec [서비스_이름] [명령어]
: 🖥️- 실행 중인 특정 서비스 컨테이너 내부에서 명령어를 실행합니다.
- 주로 컨테이너 내부로 접속하여 터미널 작업을 할 때 사용합니다.
- 예시:
docker compose exec web bash
(web 컨테이너에 bash 쉘로 접속)
-
docker compose build [서비스_이름]
: 🏗️docker-compose.yml
파일에 정의된 이미지를 명시적으로 빌드합니다.up --build
와 유사하지만,up
은 실행까지 하는 반면,build
는 이미지 빌드만 합니다.
🧪 실전 예제: Node.js + MongoDB 웹 애플리케이션
이제 실제 웹 애플리케이션을 Docker Compose로 구성하는 예시를 통해 지금까지 배운 내용을 적용해 봅시다. 간단한 Node.js 웹 서버가 MongoDB 데이터베이스에 연결되는 시나리오입니다.
프로젝트 구조:
my-node-app/
├── docker-compose.yml
├── web/
│ ├── Dockerfile
│ └── app.js
└── .env
1. web/Dockerfile
: (Node.js 웹 서버 이미지 빌드용)
# web/Dockerfile
FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
2. web/app.js
: (간단한 Node.js 웹 서버)
// web/app.js
const express = require('express');
const mongoose = require('mongoose');
const app = express();
const port = 3000;
// 환경 변수에서 MongoDB 연결 정보 가져오기
const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/mydatabase';
mongoose.connect(mongoUrl)
.then(() => console.log('MongoDB Connected! 🚀'))
.catch(err => console.error('MongoDB connection error:', err));
app.get('/', (req, res) => {
res.send('Hello from Node.js Web App! Connected to MongoDB. 🎉');
});
app.listen(port, () => {
console.log(`Web app listening at http://localhost:${port}`);
});
package.json
파일에 express
와 mongoose
를 추가하고 npm install
을 실행해야 합니다.
3. .env
: (환경 변수 파일)
MONGO_URL=mongodb://mongo:27017/mydatabase
Docker Compose는 .env
파일을 자동으로 로드하여 환경 변수로 사용합니다. 여기서는 서비스 이름인 mongo
를 호스트로 사용합니다.
4. docker-compose.yml
: (핵심!)
# docker-compose.yml
version: '3.8'
services:
web:
build: ./web # './web' 디렉터리의 Dockerfile로 이미지 빌드
ports:
- "3000:3000" # 호스트 3000번 포트를 컨테이너 3000번 포트에 매핑
environment:
- MONGO_URL=mongodb://mongo:27017/mydatabase # MongoDB 연결 URL 환경 변수 설정
depends_on:
- mongo # 'mongo' 서비스가 먼저 시작되도록 의존성 설정
volumes:
- ./web:/usr/src/app # 호스트의 './web' 디렉터리를 컨테이너에 마운트 (개발 시 코드 변경 자동 반영)
networks:
- app_network # 'app_network'에 연결
mongo:
image: mongo:latest # 최신 MongoDB 이미지 사용
ports:
- "27017:27017" # 호스트 27017번 포트를 컨테이너 27017번 포트에 매핑 (옵션: 외부 접속용)
volumes:
- mongo_data:/data/db # 'mongo_data' 볼륨을 사용하여 MongoDB 데이터 영속성 유지
networks:
- app_network # 'app_network'에 연결
networks:
app_network:
driver: bridge # 사용자 정의 브릿지 네트워크
volumes:
mongo_data: # MongoDB 데이터를 위한 명명된 볼륨
실행 방법:
-
my-node-app
디렉터리로 이동합니다. -
터미널에서 다음 명령어를 실행합니다:
docker compose up -d --build
up
: 서비스 시작-d
: 백그라운드에서 실행--build
:web
서비스의 이미지를 다시 빌드 (첫 실행 시 필수)
-
모든 컨테이너가 정상적으로 실행되는지 확인합니다:
docker compose ps
State
가Up
으로 표시되면 성공입니다! -
웹 브라우저에서
http://localhost:3000
에 접속하면, “Hello from Node.js Web App! Connected to MongoDB. 🎉” 메시지를 볼 수 있습니다. 🌐 -
로그를 확인하고 싶다면:
docker compose logs -f web
-
애플리케이션을 중지하고 컨테이너를 제거하려면:
docker compose down
데이터 볼륨까지 제거하려면:
docker compose down -v
👍 Docker Compose 사용의 장점
- 간결성 (Conciseness) ✨: 복잡한
docker run
명령어를 여러 개 쓰는 대신, 단 하나의 YAML 파일로 전체 애플리케이션 스택을 정의합니다. - 재현성 (Reproducibility) 🧪: 팀원 모두가 동일한 개발 환경을 몇 초 만에 구축할 수 있습니다. “내 컴퓨터에서는 되는데…” 같은 문제 발생 확률을 줄여줍니다.
- 협업 용이성 (Easy Collaboration) 🤝: 새로운 팀원이 합류하거나 다른 프로젝트로 전환할 때,
docker-compose.yml
파일 하나만 있으면 바로 개발 환경을 설정할 수 있습니다. - 일관된 개발 환경 (Consistent Dev Environment) 🚀: 로컬 개발, 테스트, CI/CD 파이프라인 등 모든 환경에서 동일한 방식으로 애플리케이션을 실행하고 테스트할 수 있습니다.
🗺️ 언제 Docker Compose를 사용해야 할까요?
- 로컬 개발 환경 구축: 가장 일반적이고 강력한 사용 사례입니다. 복잡한 개발 스택을
docker compose up
한 줄로 띄울 수 있습니다. - 테스트 환경: 통합 테스트나 E2E 테스트를 위해 필요한 모든 종속성(DB, 캐시 등)을 컨테이너로 띄우고 테스트를 실행할 때 유용합니다.
- CI/CD 파이프라인: 빌드 및 테스트 단계에서 필요한 서비스를 간편하게 구성할 수 있습니다.
- 소규모 프로덕션: 복잡한 확장성이나 고가용성이 크게 요구되지 않는 소규모 애플리케이션의 경우, Docker Compose로도 충분히 프로덕션 환경을 구성할 수 있습니다. 하지만 이는 제한적인 경우이며, 대규모/고가용성이 필요한 서비스는 Kubernetes와 같은 전용 오케스트레이션 도구를 고려해야 합니다.
🏁 마무리하며: Docker Compose, 당신의 컨테이너 지휘자!
오늘 우리는 Docker Compose가 무엇인지부터 왜 필요한지, 그리고 어떻게 사용하는지까지 자세히 알아보았습니다. docker-compose.yml
파일 하나로 복잡한 멀티 컨테이너 애플리케이션을 손쉽게 관리하고, docker compose up
단 한 줄로 전체 스택을 실행할 수 있다는 것이 얼마나 큰 장점인지 실감하셨기를 바랍니다.
Docker Compose는 개발 환경을 표준화하고, 팀 생산성을 향상시키며, 애플리케이션 배포 과정을 간소화하는 데 핵심적인 도구입니다. 이 글을 통해 Docker Compose에 대한 ‘완벽한 이해’에 한 걸음 더 다가가셨기를 바라며, 이제 여러분의 프로젝트에 Docker Compose의 마법을 적용해 보세요! ✨
다음번에는 더 복잡한 컨테이너 오케스트레이션 도구인 Kubernetes에 대해 알아보는 시간을 가질 수도 있겠네요! 그때까지 즐거운 코딩 되세요! 💻👋