복잡한 로컬 개발 환경 설정에 지치셨나요? Docker Compose로 여러 서비스 환경을 한 번에 구축하고 관리하는 실전 노하우를 친절하게 알려드립니다.
개발자라면 누구나 한 번쯤 이런 경험 해보셨을 거예요. 새로운 프로젝트를 시작할 때마다, 혹은 팀에 새로 합류할 때마다 개발 환경 설정 때문에 시간을 허비하고 스트레스받았던 기억 말이죠. "내 컴퓨터에서는 잘 되는데 왜 네 컴퓨터에서는 안 될까?" 하는 답답한 상황도 익숙하실 텐데요.
데이터베이스, 캐시 서버, 메시지 큐 등 다양한 서비스들을 개별적으로 설치하고 설정하는 과정은 생각만 해도 복잡하고 지루하죠. 게다가 버전 충돌이라도 나면, 문제 해결에 며칠을 쏟아붓는 일도 다반사고요. 이런 고민들, 이제 Docker Compose가 깔끔하게 해결해 줄 수 있답니다!
이 글에서는 Docker Compose를 활용해서 효율적인 로컬 개발 환경을 구축하는 방법에 대해 A부터 Z까지 친절하게 알려드릴 거예요. 개념부터 실전 예시, 그리고 유용한 팁까지 모두 담았으니, 끝까지 함께 따라오시면 여러분의 개발 생산성이 한 단계 업그레이드될 거라고 확신합니다!
📑 목차
- 왜 로컬 개발 환경에 Docker Compose가 필요할까요?
- 개발 환경의 고통스러운 현실
- Docker Compose가 제공하는 마법 같은 해결책
- Docker Compose, 이것만 알면 시작할 수 있어요! (기본 개념)
- docker-compose.yml 파일의 핵심 구성 요소
- 간단한 docker-compose.yml 예시
- 자주 사용하는 Docker Compose 명령어
- 실전! 백엔드 & 데이터베이스 환경 구축하기 (Node.js + PostgreSQL 예시)
- 프로젝트 구조 준비하기
- docker-compose.yml 작성하기
- 실행 및 확인
- 프론트엔드 환경까지 완벽하게 통합하기 (React + Nginx 예시)
- 프론트엔드 서비스 구성 전략
- 프로젝트 구조 확장 및 Dockerfile 준비
- docker-compose.yml 통합하기
- Docker Compose 활용 팁: 더 효율적인 개발을 위해!
- docker-compose.override.yml 활용 (환경 분리)
- .env 파일로 환경 변수 관리
- healthcheck로 서비스 상태 정확히 파악하기
- restart 정책 설정
- 빌드 캐싱 활용으로 시간 절약
- Hot-reloading 설정
- 마무리하며: Docker Compose로 개발 생산성을 한 단계 업그레이드!
Image by Olga_Fil on Pixabay
왜 로컬 개발 환경에 Docker Compose가 필요할까요?
본격적으로 시작하기 전에, 왜 우리가 Docker Compose에 주목해야 하는지 그 이유부터 살펴볼까요?
개발 환경의 고통스러운 현실
예전에는 서버 개발을 위해 MySQL, Redis, Kafka 등을 로컬에 직접 설치하는 경우가 많았죠. 하지만 이런 방식은 여러 문제를 야기하곤 했어요.
- 의존성 지옥 (Dependency Hell): 프로젝트마다 요구하는 라이브러리나 서비스 버전이 다르면, 충돌이 발생하기 쉽죠. 예를 들어, 한 프로젝트는 MySQL 5.7을, 다른 프로젝트는 MySQL 8.0을 요구한다면요.
- '내 컴퓨터에서는 되는데...' 문제: 각 개발자의 환경이 미묘하게 다르기 때문에, 특정 개발자 컴퓨터에서만 버그가 발생하는 상황을 마주치곤 합니다. 이는 디버깅을 더욱 어렵게 만들죠.
- 복잡한 온보딩: 새로운 팀원이 합류했을 때, 개발 환경을 설정하는 데만 며칠이 걸리는 경우도 흔해요. 모든 의존성을 수동으로 설치하고 설정하는 과정은 진입 장벽이 높을 수밖에 없습니다.
- 잦은 재설치와 초기화: 개발 중 시스템 문제가 발생하거나, 완전히 새로운 환경이 필요할 때마다 모든 것을 다시 설치해야 하는 번거로움도 무시할 수 없고요.
Docker Compose가 제공하는 마법 같은 해결책
이런 문제들을 Docker Compose는 어떻게 해결할까요? 핵심은 바로 '컨테이너화된 서비스들의 오케스트레이션'에 있습니다.
- 일관성: Docker Compose는 YAML 파일 하나로 모든 서비스의 설정과 의존성을 정의해요. 덕분에 '어떤 환경에서든 동일하게 동작'하는 완벽한 개발 환경을 구축할 수 있습니다. 운영 환경과도 최대한 유사하게 만들 수 있어 배포 시 발생할 수 있는 문제도 줄여주죠.
- 격리성: 각 서비스는 독립적인 컨테이너 안에서 실행되기 때문에, 서로 간에 전혀 간섭하지 않습니다. MySQL 버전이 충돌할 일도, Node.js 버전이 뒤섞일 일도 없다는 뜻이죠.
- 쉬운 관리: 복잡한 여러 명령어 대신,
docker-compose up명령어 하나로 모든 서비스를 한 번에 띄울 수 있어요. 서비스 중단, 재시작, 로그 확인 등 모든 관리가 훨씬 쉬워집니다. - 빠른 온보딩: 새로운 팀원이 오면, Docker와 Docker Compose만 설치하고 프로젝트의
docker-compose.yml파일이 있는 디렉토리에서 명령어 몇 줄만 입력하면 바로 개발을 시작할 수 있습니다. 개발 준비 시간이 획기적으로 단축되는 거죠!
이렇게 Docker Compose는 개발 환경의 일관성, 관리의 용이성, 그리고 빠른 온보딩이라는 강력한 장점들을 제공하며 우리의 개발 생산성을 비약적으로 높여줍니다.
Docker Compose, 이것만 알면 시작할 수 있어요! (기본 개념)
Docker Compose의 핵심은 모든 서비스 구성을 하나의 YAML 파일에 정의하는 것입니다. 이 파일은 보통 docker-compose.yml이라는 이름으로 프로젝트 루트에 위치하죠. 그럼 이 파일의 주요 구성 요소들을 한번 살펴볼까요?
docker-compose.yml 파일의 핵심 구성 요소
docker-compose.yml 파일은 크게 다음과 같은 섹션들로 이루어져 있습니다.
version: Docker Compose 파일 포맷 버전을 지정합니다. 일반적으로 최신 버전인'3.8'또는'3.9'를 사용하죠.services: Docker Compose로 관리할 애플리케이션의 '서비스'들을 정의하는 곳이에요. 웹 서버, 데이터베이스, 백엔드 API 서버 등이 각각 하나의 서비스가 될 수 있습니다.volumes: 컨테이너가 종료되어도 데이터가 사라지지 않도록 영구적으로 데이터를 저장할 공간을 정의합니다. 데이터베이스 데이터나 로그 파일 등에 필수적이죠.networks: 서비스 간 통신을 위한 네트워크를 정의합니다. 별도로 지정하지 않으면 기본 네트워크가 생성되지만, 복잡한 환경에서는 명시적으로 정의하는 것이 좋습니다.
각 서비스는 다음과 같은 속성들을 가질 수 있어요.
image: 사용할 Docker 이미지 이름을 지정합니다 (예:nginx:latest,postgres:13).build: Dockerfile을 통해 직접 이미지를 빌드해야 할 때 사용합니다.context는 Dockerfile이 있는 경로,dockerfile은 Dockerfile 이름을 지정하죠.ports: 호스트 머신(로컬 컴퓨터)과 컨테이너 간의 포트 매핑을 설정합니다 (예:"80:80"은 로컬 80번 포트를 컨테이너의 80번 포트에 연결).volumes: 호스트 파일 시스템의 특정 경로를 컨테이너 내부로 마운트합니다. 코드 수정 시 컨테이너를 다시 빌드할 필요 없이 바로 반영되도록 할 때 유용하죠. (예:./app:/app)environment: 컨테이너 내부에서 사용할 환경 변수를 설정합니다 (예: 데이터베이스 접속 정보, API 키 등).depends_on: 서비스 간의 의존성을 정의합니다. 예를 들어, 웹 애플리케이션 서비스가 데이터베이스 서비스보다 먼저 시작되어야 할 때 사용하죠.
간단한 docker-compose.yml 예시
아래는 Nginx 웹 서버와 간단한 정적 웹 페이지를 서빙하는 예시입니다.
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "80:80" # 로컬 80 포트를 컨테이너의 80 포트에 연결
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # 커스텀 Nginx 설정 파일 마운트
- ./html:/usr/share/nginx/html # 정적 파일 마운트 (index.html 등)
container_name: my-nginx-web-server
이 예시에서는 web이라는 이름의 서비스를 정의하고, nginx:latest 이미지를 사용하며, 로컬 80번 포트를 컨테이너 80번 포트에 연결합니다. 또한, 로컬의 nginx.conf와 html 폴더를 컨테이너 내부로 마운트하여 설정 파일과 정적 웹 페이지를 쉽게 변경할 수 있게 했죠.
자주 사용하는 Docker Compose 명령어
docker-compose.yml 파일을 작성했다면, 이제 몇 가지 명령어로 서비스를 관리할 수 있어요.
docker-compose up:docker-compose.yml파일에 정의된 모든 서비스를 백그라운드(-d옵션 사용 시) 또는 포그라운드에서 시작하고 컨테이너를 생성합니다.--build옵션을 추가하면 이미지도 다시 빌드하죠.docker-compose down:up으로 생성된 모든 컨테이너, 네트워크, 볼륨(-v옵션 사용 시)을 중지하고 제거합니다.docker-compose ps: 현재 Docker Compose 프로젝트의 서비스 상태를 확인합니다.docker-compose logs [서비스명]: 특정 서비스의 로그를 확인합니다.docker-compose exec [서비스명] [명령어]: 실행 중인 특정 서비스 컨테이너 내부에서 명령어를 실행합니다 (예:docker-compose exec web bash).
어때요? 생각보다 어렵지 않죠? 이제 실제 개발 시나리오에 적용해볼 시간입니다!
실전! 백엔드 & 데이터베이스 환경 구축하기 (Node.js + PostgreSQL 예시)
가장 흔하게 접하는 개발 환경 중 하나인 Node.js 백엔드와 PostgreSQL 데이터베이스 조합을 Docker Compose로 구축하는 과정을 함께 해볼까요? 이를 통해 복잡한 다중 서비스 환경을 얼마나 쉽게 구성할 수 있는지 직접 경험하실 수 있을 거예요.
프로젝트 구조 준비하기
먼저, 아래와 같이 프로젝트 폴더를 생성하고 필요한 파일들을 준비합니다.
my-backend-project/
├── backend/
│ ├── Dockerfile # Node.js 애플리케이션용 Dockerfile
│ ├── package.json
│ ├── index.js # 간단한 Node.js 앱
│ └── ...
├── docker-compose.yml # Docker Compose 설정 파일
└── .env # 환경 변수 파일 (선택 사항)
backend/Dockerfile 예시:
# backend/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "npm", "start" ]
backend/index.js (간단한 Node.js Express 앱):
// backend/index.js
const express = require('express');
const { Client } = require('pg');
require('dotenv').config(); // .env 파일 사용 (선택 사항)
const app = express();
const port = process.env.PORT || 3000;
const dbConfig = {
user: process.env.POSTGRES_USER || 'user',
host: process.env.POSTGRES_HOST || 'db', // Docker Compose 서비스 이름
database: process.env.POSTGRES_DB || 'mydb',
password: process.env.POSTGRES_PASSWORD || 'password',
port: process.env.POSTGRES_PORT || 5432,
};
app.get('/', async (req, res) => {
try {
const client = new Client(dbConfig);
await client.connect();
const result = await client.query('SELECT NOW()');
await client.end();
res.send(`Hello from Backend! Current time from DB: ${result.rows[0].now}`);
} catch (err) {
console.error('Database connection error:', err.stack);
res.status(500).send('Database connection failed');
}
});
app.listen(port, () => {
console.log(`Backend app listening at http://localhost:${port}`);
});
backend/package.json:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3",
"dotenv": "^16.3.1"
}
}
.env 파일 (루트 디렉토리):
# .env
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydatabase
POSTGRES_HOST=db # Docker Compose 서비스 이름
POSTGRES_PORT=5432
PORT=3000
docker-compose.yml 작성하기
이제 Node.js 백엔드와 PostgreSQL 데이터베이스를 정의하는 docker-compose.yml 파일을 작성해볼까요?
# docker-compose.yml
version: '3.8'
services:
backend:
build:
context: ./backend # backend 폴더에서 Dockerfile을 찾음
dockerfile: Dockerfile
ports:
- "3000:3000" # 로컬 3000 포트를 컨테이너 3000 포트에 연결
volumes:
- ./backend:/app # 호스트의 backend 코드를 컨테이너 내부 /app에 마운트
- /app/node_modules # node_modules는 호스트에 마운트되지 않도록 제외 (성능 향상)
environment:
# .env 파일에서 환경 변수를 자동으로 로드 (Docker Compose v1.27+ 부터 지원)
# 또는 아래와 같이 직접 명시할 수도 있습니다.
# POSTGRES_USER: ${POSTGRES_USER}
# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# POSTGRES_DB: ${POSTGRES_DB}
# POSTGRES_HOST: db # db 서비스의 호스트명
# PORT: ${PORT}
env_file:
- ./.env # 루트 디렉토리의 .env 파일을 사용
depends_on:
- db # backend 서비스는 db 서비스에 의존
db:
image: postgres:13
ports:
- "5432:5432" # 로컬 5432 포트를 컨테이너 5432 포트에 연결 (선택 사항)
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTES_PASSWORD}
env_file:
- ./.env
volumes:
- db_data:/var/lib/postgresql/data # 데이터 영속성을 위한 볼륨 마운트
healthcheck: # DB 준비 상태 확인
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
container_name: postgres_db_dev
volumes:
db_data: # db_data 볼륨 정의
이 파일에서 주목할 점은 다음과 같아요.
backend서비스:build섹션에서./backend폴더의Dockerfile을 사용해 이미지를 빌드합니다.ports: "3000:3000"으로 로컬에서http://localhost:3000으로 백엔드에 접근할 수 있게 합니다.volumes: ./backend:/app으로 호스트의 소스 코드를 컨테이너에 마운트하여, 코드 수정 시 컨테이너를 다시 빌드하지 않아도 바로 반영되도록 합니다./app/node_modules를 제외하여 호스트의node_modules가 컨테이너로 마운트되어 발생하는 문제를 방지하고 컨테이너 내부의node_modules를 사용하게 합니다.env_file: ./.env를 통해 루트 디렉토리의.env파일에서 환경 변수를 자동으로 가져오게 합니다.depends_on: - db를 설정하여backend서비스가db서비스가 시작된 후에 시작되도록 합니다.
db서비스:postgres:13이미지를 사용합니다.environment에 DB 접속 정보를 설정합니다. 여기서 중요한 것은 이 정보가.env파일의 변수들을 사용한다는 점이에요.volumes: db_data:/var/lib/postgresql/data를 통해db_data라는 명명된 볼륨(Named Volume)을 사용하여 데이터베이스 데이터가 컨테이너가 삭제되어도 사라지지 않도록 영속성을 확보합니다.healthcheck를 추가하여 데이터베이스가 완전히 준비되었는지 확인합니다.backend서비스가db서비스에 의존하더라도,db컨테이너가 시작되었다고 바로 연결 가능한 것은 아니거든요. 실제로 DB 서버가 구동되고 연결을 받을 준비가 되었는지 확인하는 것이 중요합니다.
실행 및 확인
이제 프로젝트 루트 디렉토리(docker-compose.yml이 있는 곳)에서 다음 명령어를 실행해 보세요.
docker-compose up --build -d
up: 서비스들을 시작합니다.--build: 새로운 변경 사항이 있다면 이미지를 다시 빌드합니다 (특히backend서비스의Dockerfile변경 시).-d: 백그라운드에서 컨테이너를 실행합니다.
모든 서비스가 성공적으로 시작되면, http://localhost:3000으로 접속하여 백엔드 애플리케이션이 데이터베이스에서 시간을 잘 가져오는지 확인할 수 있습니다.
서비스 로그를 보고 싶다면 docker-compose logs -f 명령어를, 특정 서비스 로그만 보고 싶다면 docker-compose logs -f backend와 같이 사용하면 됩니다. 모든 서비스를 중지하고 제거하고 싶다면 docker-compose down -v 명령어를 사용하세요 (-v는 볼륨까지 제거합니다).
Image by congerdesign on Pixabay
프론트엔드 환경까지 완벽하게 통합하기 (React + Nginx 예시)
백엔드와 데이터베이스 환경을 구축했으니, 이제 프론트엔드까지 Docker Compose로 통합하여 풀스택 로컬 개발 환경을 완성해볼까요? 여기서는 React 앱을 예시로 들어보겠습니다.
프론트엔드 서비스 구성 전략
프론트엔드를 Docker Compose에 통합하는 방법은 크게 두 가지로 나눌 수 있어요.
- 개발 서버 직접 사용: React의
create-react-app이나 Next.js 같은 프레임워크가 제공하는 개발 서버(webpack-dev-server 등)를 컨테이너에서 직접 실행하는 방식입니다. Hot-reloading 등 개발 편의성이 높죠. - Nginx로 빌드된 정적 파일 서빙: 프로덕션 환경처럼 React 앱을 빌드한 후, Nginx 컨테이너를 통해 정적 파일들을 서빙하는 방식입니다. 실제 배포 환경과 가장 유사하게 테스트할 수 있습니다.
개발 중에는 첫 번째 방식이 편리하지만, 배포 전 최종 로컬 테스트 시에는 두 번째 방식이 더 적합할 수 있어요. 여기서는 두 가지 상황을 모두 고려하여 docker-compose.yml을 구성하는 방법을 보여드릴게요.
프로젝트 구조 확장 및 Dockerfile 준비
기존 프로젝트 구조에 frontend 폴더를 추가합니다.
my-backend-project/
├── backend/
│ ├── Dockerfile
│ ├── ... (Node.js 앱)
├── frontend/
│ ├── Dockerfile.dev # 개발 서버용 Dockerfile (선택 사항)
│ ├── Dockerfile.prod # Nginx 서빙용 Dockerfile (선택 사항)
│ ├── package.json
│ ├── public/
│ └── src/ # React 앱
├── nginx/
│ └── nginx.conf # Nginx 설정 파일
├── docker-compose.yml
└── .env
frontend/Dockerfile.dev (React 개발 서버용):
# frontend/Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3001 # React 개발 서버 기본 포트
CMD ["npm", "start"] # 개발 서버 실행
frontend/Dockerfile.prod (Nginx 서빙용):
# frontend/Dockerfile.prod (멀티스테이지 빌드)
# 1. React 앱 빌드 스테이지
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build # React 앱 빌드
# 2. Nginx를 이용한 서빙 스테이지
FROM nginx:alpine
COPY nginx/nginx.conf /etc/nginx/nginx.conf # Nginx 설정 파일 복사
COPY --from=builder /app/build /usr/share/nginx/html # 빌드된 React 앱 복사
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx/nginx.conf (Nginx 설정 - API 프록시 예시):
# nginx/nginx.conf
events {
worker_connections 1024;
}
http {
server {
listen 80;
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 서비스의 3000번 포트로 전달
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
이 Nginx 설정 파일은 로컬 80번 포트로 들어오는 요청 중 /api/로 시작하는 요청은 backend 서비스의 3000번 포트로 전달하고, 그 외의 요청은 React 정적 파일을 서빙하도록 합니다. backend는 Docker Compose 내부 네트워크에서 서비스 이름으로 접근하는 것이죠!
docker-compose.yml 통합하기
이제 프론트엔드 서비스까지 포함한 docker-compose.yml 파일을 작성해볼게요. 여기서는 개발 편의성을 위해 React 개발 서버를 직접 띄우는 방식을 사용하고, Nginx는 API 프록시 역할만 하도록 구성해볼게요.
# docker-compose.yml (프론트엔드 개발 서버 + Nginx 프록시)
version: '3.8'
services:
frontend: # React 개발 서버
build:
context: ./frontend
dockerfile: Dockerfile.dev # 개발용 Dockerfile 사용
ports:
- "3001:3001" # React 개발 서버 포트
volumes:
- ./frontend:/app
- /app/node_modules
environment:
# React 앱 내부에서 백엔드 API 주소를 컨테이너 내부 이름으로 설정
# 실제 React 앱에서는 proxy 설정을 사용하거나, 명시적으로 http://localhost:80/api 로 호출해야 합니다.
# Create-React-App을 사용한다면 package.json에 "proxy": "http://nginx" 추가
REACT_APP_API_URL: http://nginx:80/api # Nginx 컨테이너로 API 요청
depends_on:
- nginx # Nginx가 먼저 시작되어야 프록시가 가능
nginx: # API 프록시 및 정적 파일 서빙 (나중에 frontend를 build 해서 서빙 가능)
build:
context: ./nginx
dockerfile: Dockerfile # Nginx 이미지를 사용해도 되지만, 커스텀 설정을 위해 빌드
ports:
- "80:80" # 로컬 80 포트를 Nginx 컨테이너 80 포트에 연결
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Nginx 설정 파일 마운트 (읽기 전용)
# - ./frontend/build:/usr/share/nginx/html # 프론트엔드 빌드 결과물을 마운트할 때 (배포 전 테스트용)
depends_on:
- backend # Nginx가 백엔드에 의존
backend: # 기존 백엔드 서비스
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3000:3000" # 외부에서 직접 백엔드 접근 가능 (선택 사항)
volumes:
- ./backend:/app
- /app/node_modules
env_file:
- ./.env
depends_on:
- db
db: # 기존 데이터베이스 서비스
image: postgres:13
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
env_file:
- ./.env
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data:
이 구성에서는 다음과 같은 흐름으로 서비스가 동작합니다.
frontend컨테이너에서 React 개발 서버가 3001번 포트로 실행됩니다.- 로컬에서
http://localhost:3001로 React 앱에 접근할 수 있습니다. - React 앱에서
/api/로 시작하는 요청을 보내면, 이는nginx컨테이너(로컬 80번 포트)로 전달됩니다. nginx컨테이너는 이 요청을backend컨테이너의 3000번 포트로 프록시합니다.backend컨테이너는db컨테이너와 통신하여 데이터를 처리합니다.
개발 중과 배포 전 로컬 테스트 상황에 따른 프론트엔드 서비스 구성 비교를 표로 정리해볼게요.
| 상황 | 서비스 구성 | 장점 | 단점 |
|---|---|---|---|
| 개발 중 (잦은 코드 변경) |
프론트엔드 개발 서버 직접 컨테이너 실행 (예: React 개발 서버 3001번 포트) |
|
|
| 배포 전 로컬 테스트 (빌드된 결과물 확인) |
Nginx를 통해 빌드된 프론트엔드 정적 파일 서빙 (예: Nginx 80번 포트) |
|
|
상황에 따라 docker-compose.yml 파일의 프론트엔드 서비스를 유연하게 변경하여 사용할 수 있겠죠? 예를 들어, frontend 서비스의 build 섹션에서 Dockerfile.dev 대신 Dockerfile.prod를 사용하고, nginx 서비스의 volumes에 빌드된 프론트엔드 파일을 마운트하는 방식으로 전환할 수 있습니다.
Image by Boskampi on Pixabay
Docker Compose 활용 팁: 더 효율적인 개발을 위해!
Docker Compose의 기본을 익혔다면, 이제 몇 가지 팁을 통해 여러분의 개발 환경을 더욱 효율적으로 만들어볼까요?
docker-compose.override.yml 활용 (환경 분리)
개발 환경과 운영 환경(혹은 테스트 환경)은 요구사항이 다를 때가 많죠. 예를 들어, 개발 중에는 코드를 바로 반영하기 위해 볼륨 마운트가 필요하지만, 운영 환경에서는 빌드된 이미지를 사용하는 것이 일반적입니다. 이럴 때 docker-compose.override.yml 파일을 활용하면 좋아요.
기본적인 공통 설정은 docker-compose.yml에 정의하고, 개발 환경에 특화된 설정(예: 볼륨 마운트, 특정 포트 매핑)은 docker-compose.override.yml에 정의하는 방식이에요. Docker Compose는 기본적으로 docker-compose.yml과 docker-compose.override.yml을 함께 읽어서 적용하거든요. 다른 이름을 사용하고 싶다면 -f 옵션으로 여러 파일을 지정할 수도 있습니다.
# 개발 환경 실행 시 (두 파일 모두 사용)
docker-compose up -d
# 운영 환경 실행 시 (docker-compose.prod.yml만 사용)
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
.env 파일로 환경 변수 관리
데이터베이스 비밀번호나 API 키 같은 민감한 정보는 docker-compose.yml 파일에 직접 노출하지 않는 것이 좋습니다. 대신, .env 파일을 사용하면 환경 변수를 깔끔하게 관리할 수 있어요. Docker Compose는 docker-compose.yml 파일과 같은 디렉토리에 있는 .env 파일을 자동으로 읽어와서 ${VARIABLE_NAME} 형식으로 참조되는 변수들을 채워줍니다.
앞선 예시에서 env_file: ./.env를 사용했던 것을 기억하시죠? 이렇게 하면 .env 파일만 .gitignore에 추가하여 버전 관리에서 제외하고, 팀원들에게는 .env.example 파일을 제공하여 필요한 변수들을 쉽게 알 수 있도록 할 수 있습니다.
healthcheck로 서비스 상태 정확히 파악하기
depends_on은 단순히 컨테이너 시작 순서만 보장할 뿐, 서비스가 실제로 '준비'되었는지는 알려주지 않습니다. 데이터베이스가 시작은 했지만, 아직 연결을 받을 준비가 안 되었을 수도 있거든요. 이때 healthcheck를 사용하면 특정 서비스가 실제로 건강한 상태인지 주기적으로 검사할 수 있습니다.
services:
db:
# ...
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s # 10초마다 검사
timeout: 5s # 5초 안에 응답 없으면 실패
retries: 5 # 5번 연속 실패 시 unhealthy
# ...
healthcheck를 정의하면 docker-compose ps 명령어로 서비스의 상태를 확인할 때 (healthy) 또는 (unhealthy)와 같은 정보를 함께 볼 수 있어, 디버깅에 큰 도움이 됩니다.
restart 정책 설정
컨테이너가 예기치 않게 종료되었을 때 자동으로 다시 시작하도록 설정할 수 있습니다. 특히 개발 중 실수로 컨테이너가 꺼지거나, 서비스 간의 일시적인 문제로 종료되었을 때 유용하죠.
services:
backend:
# ...
restart: always # 항상 재시작 시도 (unless-stopped, on-failure 등 옵션 존재)
no: 재시작하지 않습니다 (기본값).on-failure: 컨테이너가 에러 코드와 함께 종료될 때만 재시작합니다.always: 항상 재시작합니다 (수동으로 중지할 때까지).unless-stopped: 수동으로 중지하지 않는 한 항상 재시작합니다.
빌드 캐싱 활용으로 시간 절약
Dockerfile을 사용해서 이미지를 빌드할 때, Docker는 각 단계를 캐싱합니다. 따라서 Dockerfile의 내용 중 변경되지 않은 부분은 다시 빌드하지 않고 캐시를 재사용하죠. Dockerfile을 작성할 때 변경 빈도가 낮은 레이어(예: 의존성 설치)를 위쪽에, 변경 빈도가 높은 레이어(예: 소스 코드 복사)를 아래쪽에 배치하면 빌드 시간을 크게 단축할 수 있습니다.
# 변경 빈도 낮은 부분 (라이브러리 설치)
COPY package*.json ./
RUN npm install
# 변경 빈도 높은 부분 (소스 코드)
COPY . .
이렇게 하면 소스 코드만 변경되었을 때 npm install 과정을 다시 거치지 않아도 됩니다.
Hot-reloading 설정
웹 애플리케이션 개발 시, 코드 변경 시 자동으로 애플리케이션이 재시작되거나 새로고침되는 Hot-reloading은 필수적인 기능이죠. Docker Compose 환경에서도 이를 구현할 수 있습니다.
- Node.js (백엔드):
nodemon같은 도구를 사용하여 파일 변경을 감지하고 서버를 자동으로 재시작하도록 설정할 수 있습니다.package.json에"dev": "nodemon index.js"와 같이 스크립트를 추가하고,Dockerfile에서CMD ["npm", "run", "dev"]로 변경하면 됩니다. - React (프론트엔드): 대부분의 React 개발 서버는 기본적으로 Hot-reloading을 지원합니다.
volumes설정을 통해 호스트의 코드를 컨테이너에 마운트하면, 호스트에서 코드를 수정할 때 컨테이너 내부의 개발 서버가 변경을 감지하고 자동으로 새로고침해 줄 거예요.
마무리하며: Docker Compose로 개발 생산성을 한 단계 업그레이드!
지금까지 Docker Compose를 활용하여 효율적인 로컬 개발 환경을 구축하는 방법에 대해 자세히 알아보았습니다. 복잡한 의존성 관리, '내 컴퓨터에서는 되는데...' 문제, 그리고 새로운 팀원 온보딩의 어려움 등 개발 환경 설정에서 오는 수많은 고통들을 Docker Compose가 어떻게 해결해 주는지 확인하셨을 거예요.
Docker Compose는 일관된 개발 환경, 간편한 서비스 관리, 그리고 빠른 개발 준비 시간이라는 강력한 장점들을 제공합니다. YAML 파일 하나로 모든 서비스를 정의하고 관리하는 방식은 개발자가 인프라 설정보다는 핵심 비즈니스 로직 개발에 더 집중할 수 있도록 돕는 아주 중요한 도구라고 할 수 있죠.
처음에는 docker-compose.yml 파일 작성이 조금 생소하게 느껴질 수도 있지만, 몇 번의 시도를 통해 익숙해지면 여러분의 개발 생산성을 획기적으로 높여줄 거예요. 이제 Docker Compose로 개발 환경 스트레스는 그만! 더 즐겁고 생산적인 개발에 집중해 보세요.
여러분은 Docker Compose를 어떻게 활용하고 계신가요? 자신만의 꿀팁이나 궁금한 점이 있다면 언제든지 댓글로 공유해 주세요! 이 글이 여러분의 로컬 개발 환경 구축에 도움이 되었기를 바랍니다!
📌 함께 읽으면 좋은 글
- [클라우드 인프라] 쿠버네티스 서비스 메시 도입 전략: Istio와 Linkerd 비교 분석
- [기술 리뷰] Spring Boot, NestJS, FastAPI 비교 분석: 마이크로서비스 백엔드 프레임워크 선택 가이드
- [이슈 분석] 개발자 커리어 안정성 확보 전략: 불확실성 속에서도 빛나는 성장 로드맵
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| ESLint, Prettier, Husky, lint-staged 통합 설정 가이드: 코드 품질과 컨벤션 유지를 위한 완벽 전략 (0) | 2026.07.04 |
|---|---|
| VS Code Dev Containers 완벽 가이드: 컨테이너 기반 개발 환경 구축 마스터하기 (1) | 2026.07.03 |
| Ollama를 이용한 로컬 LLM 모델 실행 및 개발 완벽 가이드 (0) | 2026.07.02 |
| GitHub Actions로 CI/CD 파이프라인 구축: 배포 자동화 실전 가이드 (1) | 2026.06.30 |
| FastAPI를 활용한 고성능 RESTful API 개발 및 배포 가이드 (0) | 2026.06.30 |