Docker Compose를 활용해 복잡한 다중 서비스 로컬 개발 환경을 쉽고 효율적으로 구축하는 방법을 배우세요. 컨테이너 기반 개발의 핵심을 실전 예제로 익혀보세요.
📑 목차
- 안녕하세요, 개발자 여러분! 복잡한 개발 환경에 지치셨나요?
- 왜 다중 서비스 환경에 Docker Compose가 필요할까요?
- Docker Compose 핵심 개념 파헤치기
- docker-compose.yml: 모든 것의 시작
- 주요 구성 요소 설명
- build vs image 비교
- 실전 예제: 간단한 다중 서비스 스택 구현하기
- 프로젝트 구조 설계
- 백엔드 서비스 (Node.js/Express) 설정
- 프론트엔드 서비스 (React/Nginx) 설정
- Items from Database
- Add New Item
- 데이터베이스 서비스 (PostgreSQL) 설정
- docker-compose.yml 파일 작성 및 상세 분석
- 다중 서비스 개발 환경 실행 및 관리
- Docker Compose 활용 팁과 고급 설정
- 1. 개발/운영 환경 분리
- 2. 헬스 체크(Healthcheck) 설정으로 서비스 안정성 확보
- 3. 서비스 확장 (Scaling)
- 4. 환경 변수 파일 확장자 `.env` 활용
- 마무리하며: 이제 복잡한 개발 환경은 안녕!
Image by 2427999 on Pixabay
안녕하세요, 개발자 여러분! 복잡한 개발 환경에 지치셨나요?
여러분이 웹 애플리케이션을 개발하고 있다고 상상해 보세요. 백엔드 API 서버, 프론트엔드 웹 서버, 데이터베이스, 어쩌면 캐시 서버나 메시지 큐까지... 이 모든 서비스들을 로컬 환경에서 각각 설치하고, 포트 충돌을 피하고, 서로 연결하는 과정을 겪어보셨을 텐데요. 생각만 해도 머리가 지끈거리지 않나요? "내 컴퓨터에서는 잘 되는데..." 라는 말, 한 번쯤 해보셨을 겁니다. 😅
특히 마이크로서비스 아키텍처나 풀스택 개발을 진행할 때 이런 복잡성은 더욱 심해지죠. 각 서비스마다 다른 언어 런타임이나 데이터베이스 버전을 요구하기도 하고, 팀원마다 개발 환경이 달라 생기는 비효율성도 무시할 수 없어요.
하지만 걱정 마세요! 이 모든 복잡성을 한 방에 해결해 줄 든든한 친구가 있답니다. 바로 Docker Compose입니다! Docker Compose는 여러 개의 도커 컨테이너를 하나의 서비스 묶음처럼 정의하고 관리할 수 있게 해주는 도구인데요. 오늘 이 글에서는 Docker Compose를 활용해서 다중 서비스 로컬 개발 환경을 구축하는 실전 가이드를 아주 쉽고 친근하게 알려드릴 거예요. 이 가이드를 통해 여러분의 개발 생산성이 수직 상승하는 경험을 하실 수 있을 겁니다!
왜 다중 서비스 환경에 Docker Compose가 필요할까요?
우리가 개발하는 애플리케이션은 점점 더 복잡해지고 있어요. 단순히 웹 서버 하나, 데이터베이스 하나로 끝나는 경우가 드물죠. 프론트엔드, 백엔드, 데이터베이스, 레디스(Redis) 같은 캐시, 엘라스틱서치(Elasticsearch) 같은 검색 엔진 등 다양한 서비스들이 유기적으로 연결되어 동작하는 경우가 대부분입니다.
이런 환경을 Docker Compose 없이 구축한다면 어떤 문제가 생길까요?
- 설치 지옥: 각 서비스의 런타임(Node.js, Java, Python 등)과 데이터베이스(PostgreSQL, MySQL, MongoDB 등)를 로컬에 일일이 설치해야 합니다. 버전 관리도 번거롭고요.
- 포트 충돌: 여러 서비스를 띄우다 보면 포트 충돌이 빈번하게 발생합니다. "아, 이 포트 누가 쓰고 있지?" 하면서 PID를 찾아 종료하는 일은 이제 그만!
- "내 컴퓨터에서는 되는데..." 증후군: 개발자마다 로컬 환경이 조금씩 달라, 특정 환경에서만 발생하는 버그를 잡는 데 많은 시간을 허비하게 됩니다.
- 팀원 온보딩의 어려움: 새로운 팀원이 합류했을 때, 개발 환경을 설정하는 데만 며칠이 걸리는 경우도 다반사죠.
이러한 문제들을 Docker Compose가 말끔하게 해결해 줍니다!
Docker Compose를 사용하면 다음과 같은 엄청난 장점들을 누릴 수 있어요.
- 격리된 환경: 각 서비스가 독립적인 컨테이너에서 실행되므로, 로컬 환경을 오염시키지 않고 깔끔하게 관리할 수 있습니다.
- 재현 가능한 환경: `docker-compose.yml` 파일 하나로 모든 서비스의 구성이 정의되므로, 어떤 컴퓨터에서든 동일한 개발 환경을 단 몇 줄의 명령어로 구축할 수 있습니다.
- 간편한 관리: `docker compose up`, `docker compose down` 명령어 하나로 모든 서비스를 동시에 시작하고 종료할 수 있어 매우 편리해요.
- 팀 협업 효율 증가: 모든 팀원이 동일한 환경에서 개발하므로 "내 컴퓨터에서는 되는데" 같은 문제를 줄이고, 온보딩 시간을 획기적으로 단축할 수 있습니다.
상상해 보세요. 백엔드 개발자는 프론트엔드 환경에 대해, 프론트엔드 개발자는 데이터베이스 설치에 대해 신경 쓸 필요 없이, 오직 각자의 코드에만 집중할 수 있게 되는 거죠. 정말 매력적이지 않나요? 😉
Docker Compose 핵심 개념 파헤치기
Docker Compose를 잘 활용하려면 몇 가지 핵심 개념을 이해해야 합니다. 이 친구들은 `docker-compose.yml` 파일의 기본 뼈대가 되거든요.
docker-compose.yml: 모든 것의 시작
Docker Compose는 YAML 형식의 설정 파일인 `docker-compose.yml`을 기반으로 동작합니다. 이 파일 안에 우리가 실행하고 싶은 모든 서비스와 그 서비스들의 설정(어떤 이미지를 사용할지, 어떤 포트를 열지, 어떤 환경 변수를 줄지 등)을 정의하는 거죠. 마치 복잡한 개발 환경의 청사진이라고 생각하시면 됩니다.
version: '3.8' # Docker Compose 파일 형식 버전
services: # 이 섹션에 실행할 서비스들을 정의합니다.
web:
build: .
ports:
- "80:80"
volumes:
- .:/code
db:
image: postgres:13
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
networks: # 서비스 간 통신을 위한 네트워크를 정의합니다.
app-network:
volumes: # 데이터를 영구적으로 저장하기 위한 볼륨을 정의합니다.
db-data:
주요 구성 요소 설명
위 예시에서 보셨듯이, `docker-compose.yml` 파일은 크게 `version`, `services`, `networks`, `volumes` 섹션으로 구성됩니다.
services: 이 섹션이 바로 핵심입니다. 여러분이 실행하고 싶은 각각의 애플리케이션이나 데이터베이스 같은 서비스들을 여기에 정의합니다. 각 서비스는 독립적인 컨테이너로 실행되죠. 예를 들어, 백엔드 서비스, 프론트엔드 서비스, 데이터베이스 서비스 등을 여기에 나열할 수 있습니다.networks: Docker Compose는 기본적으로 서비스 간 통신을 위한 내부 네트워크를 생성해 줍니다. 명시적으로 네트워크를 정의하면 서비스 간의 연결을 더욱 명확하게 관리할 수 있어요. 예를 들어, 백엔드가 데이터베이스에 연결할 때, 데이터베이스의 컨테이너 이름(예: `db`)을 사용하여 통신할 수 있게 해줍니다.volumes: 컨테이너는 휘발성이라는 특징이 있습니다. 컨테이너가 삭제되면 그 안에 있던 데이터도 사라진다는 의미인데요. 볼륨을 사용하면 컨테이너 내부의 데이터를 호스트 머신에 영구적으로 저장할 수 있습니다. 데이터베이스 데이터나 로그 파일 등을 저장할 때 필수적이죠.
build vs image 비교
서비스를 정의할 때 `build`와 `image` 키워드를 자주 사용하게 됩니다. 이 둘은 컨테이너 이미지를 생성하고 사용하는 방식에 차이가 있는데요.
| 구분 | build |
image |
|---|---|---|
| 용도 | Dockerfile을 사용하여 직접 이미지를 빌드할 때 | 이미 만들어진 이미지(Docker Hub 등)를 가져와 사용할 때 |
| 경로/이름 | Dockerfile이 있는 경로를 지정 (예: ., ./backend) |
Docker Hub 등의 이미지 이름과 태그 (예: postgres:13, nginx:latest) |
| 사용 예시 | 개발 중인 애플리케이션(백엔드, 프론트엔드) | 데이터베이스, 캐시 서버, 프록시 서버 등 표준화된 소프트웨어 |
| 재빌드 필요성 | Dockerfile이나 소스 코드가 변경되면 재빌드해야 함 | 이미지 태그가 변경되거나 강제로 pull하지 않는 이상 재빌드 불필요 |
일반적으로 우리가 직접 개발하는 애플리케이션은 `build`를 사용하고, PostgreSQL이나 Redis처럼 이미 잘 만들어진 범용 소프트웨어는 `image`를 사용한다고 이해하시면 편해요. 아시겠죠?
실전 예제: 간단한 다중 서비스 스택 구현하기
이제 이론은 충분히 익혔으니, 실제 예제를 통해 Docker Compose의 마법을 경험해 볼 시간입니다! 우리는 간단한 웹 애플리케이션 스택을 구축해 볼 건데요. Node.js 백엔드, React 프론트엔드, 그리고 PostgreSQL 데이터베이스로 구성된 환경을 만들어 볼 거예요.
프로젝트 구조 설계
먼저, 프로젝트의 기본적인 디렉토리 구조를 잡아볼까요? 다음과 같이 구성할 겁니다.
my-fullstack-app/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── app.js
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ ├── src/
│ └── public/
├── docker-compose.yml
└── .env
- `backend/`: Node.js 백엔드 서비스 관련 파일들이 들어갈 거예요.
- `frontend/`: React 프론트엔드 서비스 관련 파일들이 들어갈 거예요.
- `docker-compose.yml`: 우리의 다중 서비스 환경을 정의하는 핵심 파일이죠!
- `.env`: 환경 변수를 안전하게 관리하기 위한 파일입니다.
백엔드 서비스 (Node.js/Express) 설정
먼저 백엔드 서비스를 구성해 볼게요. 간단한 API 엔드포인트를 가진 Node.js Express 앱을 만들어 봅시다.
`backend/package.json`
{
"name": "backend",
"version": "1.0.0",
"description": "Simple Node.js backend for fullstack app",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
}
}
`backend/app.js`
const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 3000;
const pool = new Pool({
user: process.env.POSTGRES_USER || 'user',
host: process.env.POSTGRES_HOST || 'localhost', // Docker Compose 네트워크에서는 서비스 이름으로 접근
database: process.env.POSTGRES_DB || 'mydb',
password: process.env.POSTGRES_PASSWORD || 'password',
port: 5432,
});
app.use(express.json());
app.get('/', (req, res) => {
res.send('Hello from Backend!');
});
app.get('/items', async (req, res) => {
try {
const client = await pool.connect();
const result = await client.query('SELECT * FROM items');
client.release();
res.json(result.rows);
} catch (err) {
console.error('Error fetching items', err);
res.status(500).send('Error fetching items');
}
});
app.post('/items', async (req, res) => {
const { name, description } = req.body;
try {
const client = await pool.connect();
await client.query('INSERT INTO items (name, description) VALUES ($1, $2)', [name, description]);
client.release();
res.status(201).send('Item added successfully');
} catch (err) {
console.error('Error adding item', err);
res.status(500).send('Error adding item');
}
});
app.listen(port, () => {
console.log(`Backend server listening at http://localhost:${port}`);
});
`backend/Dockerfile`
# Node.js 런타임을 가진 기본 이미지 사용
FROM node:18-alpine
# 컨테이너 내 작업 디렉토리 설정
WORKDIR /app
# package.json과 package-lock.json 파일을 복사하여 의존성 설치
# 캐시를 활용하기 위해 먼저 복사하고 설치
COPY package*.json ./
RUN npm install
# 나머지 애플리케이션 소스 코드 복사
COPY . .
# 애플리케이션이 사용할 포트 지정
EXPOSE 3000
# 애플리케이션 실행 명령어
CMD ["npm", "start"]
프론트엔드 서비스 (React/Nginx) 설정
다음은 React 프론트엔드 서비스를 구성해 볼게요. React 앱은 `create-react-app`으로 생성했다고 가정하고, 빌드된 결과물을 Nginx를 통해 서빙할 겁니다. Nginx를 사용하는 이유는 프로덕션 환경과 유사하게 정적 파일 서빙을 처리하기 위함이에요.
먼저, `frontend` 디렉토리 안에서 `npx create-react-app .` 명령어로 React 앱을 생성해 주세요. 그리고 `frontend/src/App.js` 파일을 수정해서 백엔드와 통신하는 간단한 예제를 추가해 봅시다.
`frontend/src/App.js` (예시)
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [message, setMessage] = useState('');
const [items, setItems] = useState([]);
const [newItemName, setNewItemName] = useState('');
const [newItemDesc, setNewItemDesc] = useState('');
useEffect(() => {
// 백엔드 API에서 메시지 가져오기
fetch('/api/') // Nginx가 /api 경로를 백엔드로 프록시할 예정
.then(res => res.text())
.then(data => setMessage(data))
.catch(err => console.error('Error fetching message:', err));
// 아이템 목록 가져오기
fetch('/api/items')
.then(res => res.json())
.then(data => setItems(data))
.catch(err => console.error('Error fetching items:', err));
}, []);
const handleAddItem = () => {
fetch('/api/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: newItemName, description: newItemDesc }),
})
.then(res => res.text())
.then(data => {
console.log(data);
setNewItemName('');
setNewItemDesc('');
// 아이템 추가 후 목록 갱신
fetch('/api/items')
.then(res => res.json())
.then(data => setItems(data));
})
.catch(err => console.error('Error adding item:', err));
};
return (
Fullstack App with Docker Compose
Backend Message: {message}
Items from Database
- {items.map(item => (
- {item.name}: {item.description} (ID: {item.id}) ))}
Image by Daria-Yakovleva on Pixabay
Add New Item
setNewItemName(e.target.value)} /> setNewItemDesc(e.target.value)} />
);
}
export default App;
`frontend/Dockerfile`
# 1단계: React 앱 빌드 (Node.js 환경)
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 2단계: Nginx를 사용하여 빌드된 React 앱 서빙
FROM nginx:alpine
# Nginx 기본 설정 파일 삭제
RUN rm /etc/nginx/conf.d/default.conf
# 사용자 정의 Nginx 설정 파일 복사
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 빌드된 React 앱 정적 파일들을 Nginx 웹 루트로 복사
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
`frontend/nginx.conf`
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# /api 경로로 들어오는 요청은 백엔드 서비스로 프록시
location /api/ {
proxy_pass http://backend:3000/; # 'backend'는 Docker Compose 서비스 이름
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;
}
}
여기서 `proxy_pass http://backend:3000/;` 이 부분이 중요해요. Nginx 컨테이너에서 `backend`라는 서비스 이름으로 백엔드 컨테이너의 3000번 포트에 접근하는 것을 볼 수 있죠. Docker Compose 네트워크 덕분에 가능한 일입니다!
데이터베이스 서비스 (PostgreSQL) 설정
데이터베이스는 이미 만들어진 PostgreSQL 이미지를 사용할 거예요. 별도의 Dockerfile 없이 `docker-compose.yml`에서 바로 설정할 수 있습니다.
데이터베이스 초기 스키마를 설정하기 위해 `init.sql` 파일을 만들어 볼까요? 이 파일은 PostgreSQL 컨테이너가 처음 시작할 때 자동으로 실행됩니다.
`db/init.sql` (새 디렉토리 `db` 생성 후 파일 추가)
CREATE TABLE IF NOT EXISTS items (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO items (name, description) VALUES ('Example Item 1', 'This is the first example item.');
INSERT INTO items (name, description) VALUES ('Example Item 2', 'This is the second example item.');
docker-compose.yml 파일 작성 및 상세 분석
이제 모든 서비스에 대한 준비가 끝났으니, 이 서비스들을 하나로 묶어줄 `docker-compose.yml` 파일을 작성해 봅시다. 이 파일은 `my-fullstack-app` 디렉토리 바로 아래에 위치해야 해요.
`docker-compose.yml`
version: '3.8'
services:
# 백엔드 서비스 정의
backend:
build:
context: ./backend # backend 디렉토리의 Dockerfile을 사용
dockerfile: Dockerfile
ports:
- "3000:3000" # 호스트의 3000번 포트를 컨테이너의 3000번 포트에 연결
environment: # 환경 변수 설정
POSTGRES_HOST: db # 데이터베이스 서비스의 이름으로 접근
POSTGRES_USER: ${POSTGRES_USER} # .env 파일에서 불러옴
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./backend:/app # 호스트의 ./backend 디렉토리를 컨테이너의 /app에 마운트 (코드 변경 실시간 반영)
- /app/node_modules # node_modules는 마운트에서 제외하여 컨테이너 내부에서 관리
depends_on: # db 서비스가 먼저 시작되어야 함을 명시
- db
networks:
- app-network
# 프론트엔드 서비스 정의 (Nginx를 통해 React 빌드 파일 서빙)
frontend:
build:
context: ./frontend # frontend 디렉토리의 Dockerfile을 사용
dockerfile: Dockerfile
ports:
- "80:80" # 호스트의 80번 포트를 컨테이너의 80번 포트에 연결
depends_on: # backend 서비스가 먼저 시작되어야 함을 명시 (Nginx 프록시 설정 때문)
- backend
networks:
- app-network
# PostgreSQL 데이터베이스 서비스 정의
db:
image: postgres:13-alpine # PostgreSQL 13 버전 이미지 사용
environment: # 환경 변수 설정
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data # 데이터베이스 데이터를 저장할 볼륨 마운트
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql # 초기 스키마/데이터 설정
networks:
- app-network
# 네트워크 정의
networks:
app-network:
driver: bridge # 기본 브릿지 네트워크 사용
# 볼륨 정의
volumes:
db_data: # 데이터베이스 데이터를 저장할 named volume
각 서비스별로 핵심 설정을 자세히 살펴볼까요?
backend서비스:- `build: context: ./backend`: `backend` 디렉토리에 있는 `Dockerfile`을 사용해서 이미지를 빌드하라고 지시합니다.
- `ports: "3000:3000"`: 호스트 머신의 3000번 포트로 들어오는 요청을 `backend` 컨테이너의 3000번 포트로 전달합니다.
- `environment`: PostgreSQL 연결 정보를 환경 변수로 넘겨줍니다. 여기서 `POSTGRES_HOST: db`를 주목하세요. 같은 Docker Compose 네트워크 내에서는 서비스 이름을 호스트 이름처럼 사용할 수 있답니다!
- `volumes: ./backend:/app`: 호스트 머신의 `backend` 디렉토리(여러분의 소스 코드가 있는 곳)를 컨테이너 내부의 `/app` 디렉토리에 마운트합니다. 이렇게 하면 로컬에서 코드를 수정해도 컨테이너에 즉시 반영되어, 재빌드 없이 개발할 수 있습니다. `node_modules`는 `/app/node_modules`로 명시하여 호스트 볼륨에 포함되지 않도록 합니다.
- `depends_on: - db`: `db` 서비스가 완전히 시작된 후에 `backend` 서비스가 시작되도록 보장합니다.
frontend서비스:- `build: context: ./frontend`: `frontend` 디렉토리의 `Dockerfile`을 사용합니다. 이 Dockerfile은 React 앱을 빌드하고 Nginx로 서빙하도록 구성되어 있었죠.
- `ports: "80:80"`: 호스트의 80번 포트(웹 기본 포트)로 `frontend` 컨테이너의 80번 포트에 접근할 수 있게 합니다.
- `depends_on: - backend`: `frontend`의 Nginx가 `backend`로 프록시 요청을 보내므로, `backend`가 먼저 실행되어야 합니다.
db서비스:- `image: postgres:13-alpine`: Docker Hub에서 공식 PostgreSQL 13 버전을 가져와 사용합니다. `alpine` 버전은 용량이 작아 컨테이너 크기를 줄이는 데 유리해요.
- `environment`: PostgreSQL 데이터베이스의 이름, 사용자, 비밀번호를 설정합니다. 이 값들은 `.env` 파일에서 불러올 거예요.
- `volumes: db_data:/var/lib/postgresql/data`: 명명된 볼륨(named volume)인 `db_data`를 사용하여 PostgreSQL 데이터 파일이 영구적으로 저장되도록 합니다. 컨테이너가 삭제되어도 데이터는 보존되는 거죠.
- `volumes: ./db/init.sql:/docker-entrypoint-initdb.d/init.sql`: 컨테이너가 처음 시작될 때 `init.sql` 파일이 자동으로 실행되어 초기 테이블을 생성하고 데이터를 삽입하도록 합니다.
환경 변수 관리를 위해 `.env` 파일을 `docker-compose.yml`과 같은 위치에 생성해 주세요.
`.env`
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=myappdb
이렇게 설정하면 `docker-compose.yml` 파일에 민감한 정보(비밀번호 등)를 직접 노출하지 않고 안전하게 관리할 수 있답니다!
Image by Olga_Fil on Pixabay
다중 서비스 개발 환경 실행 및 관리
이제 모든 준비가 끝났어요! `docker-compose.yml` 파일이 있는 `my-fullstack-app` 디렉토리로 이동하여 다음 명령어를 실행하면 됩니다.
docker compose up -d
이 명령어는 다음 작업을 수행합니다.
- `backend`와 `frontend` 서비스의 `Dockerfile`을 기반으로 이미지를 빌드합니다 (처음 실행 시).
- `db` 서비스에 사용할 `postgres:13-alpine` 이미지를 다운로드합니다 (처음 실행 시).
- `app-network`라는 브릿지 네트워크를 생성합니다.
- `db_data`라는 볼륨을 생성합니다.
- `db`, `backend`, `frontend` 서비스 컨테이너를 정의된 순서(`depends_on`에 따라)대로 시작합니다.
- `-d` 옵션은 컨테이너를 백그라운드에서 실행하라는 의미입니다. 터미널을 계속 점유하지 않죠.
모든 서비스가 성공적으로 시작되었다면, 웹 브라우저를 열고 `http://localhost/`로 접속해 보세요. React 프론트엔드 앱이 보이고, 백엔드로부터 메시지를 받아오며, 데이터베이스에서 가져온 아이템 목록이 표시될 거예요!
서비스가 잘 실행되고 있는지 확인하려면 다음 명령어를 사용합니다.
docker compose ps
각 서비스의 로그를 확인하고 싶다면:
docker compose logs [서비스_이름] # 예: docker compose logs backend
또는 모든 서비스의 로그를 한 번에 보려면:
docker compose logs -f # -f는 실시간으로 로그를 계속 보여줍니다.
개발 중 코드 변경 사항을 적용하고 싶다면, `volumes` 설정을 해두었기 때문에 대부분의 경우 파일을 저장하는 것만으로 컨테이너 내부에 반영됩니다. Node.js 백엔드의 경우, `nodemon` 같은 도구를 컨테이너 내부에 설치하여 파일 변경 시 자동으로 서버를 재시작하도록 설정할 수도 있습니다. `frontend`의 React 앱은 `npm run build`를 다시 실행해야 변경 사항이 반영됩니다.
만약 `Dockerfile`이나 `docker-compose.yml` 파일 자체를 변경했다면, 이미지를 재빌드하고 서비스를 다시 시작해야 합니다.
docker compose up -d --build # --build 옵션으로 이미지를 다시 빌드합니다.
개발 환경 사용을 마치고 모든 컨테이너와 네트워크, 볼륨을 정리하고 싶다면:
docker compose down # 모든 컨테이너를 중지하고 삭제합니다. 네트워크도 삭제됩니다.
docker compose down --volumes # 컨테이너, 네트워크와 함께 named volumes(db_data)도 삭제합니다.
down --volumes 명령은 데이터베이스의 영구 데이터도 삭제하므로 주의해서 사용해야 해요! 보통 개발 환경을 완전히 초기화할 때 사용합니다.
Docker Compose 활용 팁과 고급 설정
여기까지 잘 따라오셨다면, 이미 Docker Compose의 강력함을 충분히 느끼셨을 거예요. 하지만 여기서 멈출 수 없죠! 몇 가지 활용 팁과 고급 설정을 통해 여러분의 개발 환경을 더욱 강력하게 만들어 봅시다.
1. 개발/운영 환경 분리
로컬 개발 환경과 실제 배포될 운영 환경은 요구사항이 다를 수 있잖아요. 예를 들어, 개발 환경에서는 코드 변경이 즉시 반영되어야 하고, 운영 환경에서는 성능과 안정성이 중요하죠. Docker Compose는 여러 개의 `docker-compose.yml` 파일을 조합하여 이러한 환경을 유연하게 관리할 수 있습니다.
예를 들어, `docker-compose.yml`에는 공통 설정을, `docker-compose.dev.yml`에는 개발 전용 설정을 (예: 볼륨 마운트, 디버깅 포트), `docker-compose.prod.yml`에는 운영 전용 설정을 (예: 리소스 제한, 복제본 수) 정의할 수 있어요.
`docker-compose.dev.yml` (예시)
version: '3.8'
services:
backend:
volumes:
- ./backend:/app # 개발 중 코드 변경 실시간 반영
# command: npm run dev # 개발 서버 실행 명령어
db:
ports:
- "5432:5432" # 로컬에서 직접 DB 클라이언트로 접근 가능하도록 포트 오픈
이렇게 파일을 나누고, 실행 시에는 `-f` 옵션으로 여러 파일을 지정합니다. 나중에 지정된 파일의 설정이 이전 파일의 설정을 덮어씁니다.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
2. 헬스 체크(Healthcheck) 설정으로 서비스 안정성 확보
depends_on은 서비스의 시작 순서만 보장할 뿐, 서비스가 실제로 준비 완료되었는지까지는 확인하지 못해요. 예를 들어, 백엔드 서비스가 시작됐지만 아직 데이터베이스 연결이 안 됐을 수도 있죠. 이때 헬스 체크를 사용하면 서비스의 실제 상태를 확인할 수 있습니다.
`docker-compose.yml` (backend 서비스에 healthcheck 추가 예시)
services:
backend:
# ... 기존 설정 ...
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"] # 3000번 포트의 / 경로로 curl 요청
interval: 30s # 30초마다 체크
timeout: 10s # 10초 내 응답 없으면 실패
retries: 5 # 5번 실패하면 unhealthy
start_period: 20s # 컨테이너 시작 후 20초 동안은 체크 무시
# ...
db:
# ... 기존 설정 ...
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
이제 `depends_on` 대신 `depends_on`의 `condition: service_healthy`를 사용할 수 있습니다. 이렇게 하면 `backend`는 `db`가 healthy 상태가 될 때까지 기다리게 됩니다.
services:
backend:
# ...
depends_on:
db:
condition: service_healthy # db 서비스가 healthy 상태일 때만 backend 시작
# ...
이렇게 하면 서비스 간 의존성을 더욱 견고하게 만들 수 있겠죠?
3. 서비스 확장 (Scaling)
특정 서비스의 부하가 많아 여러 개의 인스턴스를 띄워야 할 때가 있습니다. Docker Compose는 이 또한 쉽게 할 수 있도록 지원합니다.
docker compose up --scale backend=3 -d
이 명령어는 `backend` 서비스를 3개의 인스턴스로 확장하여 실행합니다. 로드 밸런싱까지는 직접 구성해야 하지만, 로컬에서 여러 인스턴스로 테스트할 때 유용하죠.
4. 환경 변수 파일 확장자 `.env` 활용
앞서 `.env` 파일을 사용했지만, Docker Compose는 `.env` 파일을 자동으로 읽어 들여 환경 변수로 사용합니다. 이 기능을 활용하면 민감한 정보뿐만 아니라, 자주 바뀌는 설정 값들을 코드와 분리하여 관리할 수 있어 편리하답니다.
이 외에도 Docker Compose는 다양한 설정 옵션과 활용법을 제공해요. 공식 문서를 참고하시면 더욱 깊이 있는 내용을 학습하실 수 있을 겁니다. 중요한 건 직접 해보면서 익숙해지는 것이겠죠!
마무리하며: 이제 복잡한 개발 환경은 안녕!
오늘은 Docker Compose를 활용해서 다중 서비스 로컬 개발 환경을 구축하는 방법에 대해 자세히 알아봤습니다. 어떠셨나요? 처음에는 조금 복잡하게 느껴질 수도 있지만, 한 번 익숙해지면 여러분의 개발 워크플로우를 혁신적으로 개선해 줄 강력한 도구라는 것을 느끼실 수 있을 거예요.
Docker Compose를 사용하면 더 이상 개발 환경 설정 때문에 골치 아플 필요가 없습니다. 재현 가능한 환경, 간편한 서비스 관리, 그리고 팀 협업의 효율성 증대까지! 이 모든 장점을 여러분의 것으로 만들 수 있답니다. 이제 "내 컴퓨터에서는 되는데..." 같은 말은 옛말이 될 거예요. 😉
이 가이드가 여러분의 개발 생활에 큰 도움이 되기를 바라며, 직접 이 예제를 따라 해보시면서 Docker Compose의 진정한 가치를 느껴보시길 강력히 추천합니다!
혹시 궁금한 점이나 더 좋은 팁이 있다면 언제든지 댓글로 공유해주세요. 함께 발전하는 개발자 커뮤니티를 만들어가요!
다음에도 더 유익하고 실용적인 개발 이야기로 찾아오겠습니다. 감사합니다!
📌 함께 읽으면 좋은 글
- [튜토리얼] Docker Compose 활용: 로컬 다중 서비스 개발 환경 완벽 구축 가이드
- [튜토리얼] Prometheus와 Grafana를 활용한 애플리케이션 성능 모니터링 시스템 구축 실전 가이드
- [튜토리얼] GitHub Actions를 활용한 웹 애플리케이션 자동 배포 CI/CD 파이프라인 구축 실전 가이드
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| GitHub Actions 활용 웹 서비스 CI/CD 파이프라인 자동화: 직접 써본 구축 노하우 (0) | 2026.05.21 |
|---|---|
| Next.js, Tailwind CSS, Shadcn UI로 모던 웹 UI 개발 환경 완벽 구축 가이드 (0) | 2026.05.20 |
| GitHub Actions를 활용한 웹 애플리케이션 자동 배포 CI/CD 파이프라인 구축 실전 가이드 (0) | 2026.05.18 |
| React와 Spring Boot 연동: 로컬 개발 환경 구축 및 API 통신 실전 가이드 (0) | 2026.05.18 |
| Prometheus와 Grafana를 활용한 애플리케이션 성능 모니터링 시스템 구축 실전 가이드 (0) | 2026.05.18 |