Docker Compose를 활용하여 복잡한 로컬 다중 서비스 개발 환경을 효율적으로 구축하는 방법을 상세히 안내합니다. 컨테이너 기반 개발의 장점을 누리고 생산성을 높이세요.
복잡한 로컬 개발 환경, 왜 문제일까요?
백엔드 API 서버, 데이터베이스, 캐시 서버, 프론트엔드 빌드 서버... 현대 웹 애플리케이션은 종종 여러 서비스로 구성됩니다. 개발자로서 이러한 다중 서비스를 로컬 환경에서 동시에 구동하고 관리해야 할 때, 다음과 같은 문제에 직면한 적이 있으신가요?
- 서로 다른 서비스의 종속성 충돌: 각 서비스가 요구하는 라이브러리 버전이 달라 개발 환경 설정에 애를 먹는 경우
- 포트 충돌: 여러 서비스를 실행하려다 이미 사용 중인 포트 때문에 곤란을 겪는 경우
- 환경 일관성 부족: 개발 환경과 실제 배포 환경이 달라 예상치 못한 버그가 발생하는 경우
- 새로운 팀원 온보딩의 어려움: 복잡한 개발 환경 설정 가이드 때문에 새로운 팀원이 프로젝트에 합류하는 데 시간이 오래 걸리는 경우
이러러한 문제들은 개발 생산성을 저해하고 불필요한 스트레스를 유발합니다. 이 글에서는 Docker Compose를 활용하여 이러한 문제들을 해결하고, 효율적이고 일관된 로컬 다중 서비스 개발 환경을 구축하는 방법을 단계별로 상세히 안내합니다. 더 이상 환경 설정에 시간을 낭비하지 않고 핵심 개발에 집중할 수 있도록 도와드리겠습니다.
Docker Compose, 당신의 구원투수
Docker Compose는 여러 개의 Docker 컨테이너를 정의하고 실행하기 위한 도구입니다. 단일 파일(일반적으로 docker-compose.yml)에 애플리케이션의 모든 서비스를 구성하고, 한 번의 명령으로 전체 애플리케이션 스택을 구동할 수 있게 해줍니다.
📑 목차
Image by Olga_Fil on Pixabay
Docker Compose의 핵심 기능과 장점
Docker Compose가 제공하는 주요 기능과 이를 통해 얻을 수 있는 이점은 다음과 같습니다.
- 단일 파일 구성: 모든 서비스, 네트워크, 볼륨 설정을
docker-compose.yml파일 하나에 정의합니다. 이는 프로젝트의 전체 아키텍처를 한눈에 파악할 수 있게 해줍니다. - 간편한 시작과 중지:
docker-compose up명령 하나로 모든 서비스를 시작하고,docker-compose down명령으로 모든 서비스를 깨끗하게 종료할 수 있습니다. - 환경 일관성: 개발 환경, 테스트 환경, 그리고 심지어 프로덕션 환경까지 동일한 Docker 이미지와 설정을 사용할 수 있어 "내 컴퓨터에서는 되는데..." 같은 문제를 방지합니다.
- 서비스 격리: 각 서비스는 독립적인 컨테이너에서 실행되므로, 한 서비스의 문제가 다른 서비스에 영향을 미치지 않습니다.
- 재현성: 팀원 모두가 동일한 개발 환경을 몇 분 안에 구축할 수 있어, 새로운 개발자 온보딩이 매우 쉬워집니다.
- 확장성: 필요에 따라 특정 서비스의 컨테이너 수를 쉽게 늘릴 수 있습니다.
이러한 장점들은 특히 마이크로서비스 아키텍처나 복잡한 다중 서비스 애플리케이션을 개발할 때 빛을 발합니다.
Docker Compose 설치 및 기본 설정
Docker Compose를 사용하기 위해서는 먼저 Docker Engine이 설치되어 있어야 합니다. Docker Desktop을 설치하면 Docker Engine과 Docker Compose가 함께 설치됩니다. 설치가 완료되었다면 터미널에서 다음 명령어를 실행하여 Docker와 Docker Compose의 설치 여부와 버전을 확인할 수 있습니다.
docker --version
docker compose version
이 글에서는 docker compose CLI 플러그인 방식을 기준으로 설명합니다. (과거에는 docker-compose 명령어를 사용했지만, 현재는 docker compose 명령어를 권장합니다.)
docker-compose.yml 파일의 기본 구조
Docker Compose의 모든 설정은 docker-compose.yml이라는 YAML 파일에 정의됩니다. 이 파일은 애플리케이션을 구성하는 서비스들(컨테이너), 네트워크, 볼륨 등을 명시합니다. 다음은 기본적인 구조입니다.
version: '3.8' # Compose 파일 형식 버전
services:
# 서비스 1 정의
service1:
image: nginx:latest # 사용할 Docker 이미지
ports:
- "80:80" # 호스트:컨테이너 포트 매핑
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # 볼륨 마운트 (호스트:컨테이너)
environment:
- EXAMPLE_ENV_VAR=value # 환경 변수 설정
depends_on:
- service2 # service2가 먼저 시작되어야 함
# 서비스 2 정의
service2:
build: ./app # Dockerfile이 있는 경로 (이미지 빌드)
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/mydb
networks:
- my-network # 특정 네트워크에 연결
networks:
my-network: # 사용자 정의 네트워크
driver: bridge
volumes:
db-data: # 영구 데이터 저장을 위한 볼륨
각 섹션의 의미는 다음과 같습니다.
- version: Compose 파일 형식의 버전을 지정합니다. 최신 기능들을 사용하기 위해 가장 높은 버전을 사용하는 것이 좋습니다. (
3.x) - services: 애플리케이션을 구성하는 개별 서비스들을 정의합니다. 각 서비스는 하나의 컨테이너로 실행됩니다.
- networks: 컨테이너들이 서로 통신할 수 있도록 사용자 정의 네트워크를 정의합니다. 명시적으로 정의하지 않으면 기본 네트워크가 생성됩니다.
- volumes: 컨테이너의 데이터를 영구적으로 저장하기 위한 볼륨을 정의합니다. 컨테이너가 삭제되어도 데이터가 보존됩니다.
이 구조를 이해하는 것은 Docker Compose를 효과적으로 사용하는 첫걸음입니다.
다중 서비스 개발 환경 설계하기
효율적인 로컬 개발 환경을 구축하기 위해서는 docker-compose.yml 파일을 잘 설계하는 것이 중요합니다. 각 서비스의 역할과 상호작용을 고려하여 신중하게 구성해야 합니다.
서비스 정의의 핵심 요소
각 서비스 블록 안에는 다음과 같은 주요 설정들이 포함됩니다.
| 설정 | 설명 | 예시 |
|---|---|---|
| image | 서비스에 사용할 Docker 이미지를 지정합니다. Docker Hub에서 가져오거나 로컬에 있는 이미지를 사용합니다. | image: postgres:14-alpine |
| build | 서비스에 사용할 이미지를 Dockerfile로부터 빌드할 때 사용합니다. Dockerfile이 있는 경로를 지정합니다. | build: ./backend |
| ports | 호스트 머신과 컨테이너 간의 포트 매핑을 설정합니다. "호스트포트:컨테이너포트" 형식입니다. |
- "8000:8000" |
| volumes | 호스트 파일 시스템의 디렉토리나 Docker 볼륨을 컨테이너에 마운트합니다. 개발 중 코드 변경 사항을 즉시 반영하거나 데이터를 영구적으로 저장할 때 유용합니다. | - ./app:/usr/src/app- db_data:/var/lib/postgresql/data |
| environment | 컨테이너 내부에서 사용할 환경 변수를 설정합니다. 데이터베이스 연결 정보, API 키 등에 활용됩니다. | - POSTGRES_DB=mydb- NODE_ENV=development |
| depends_on | 다른 서비스에 대한 의존성을 선언합니다. 지정된 서비스가 먼저 시작된 후에 현재 서비스가 시작됩니다. (시작 순서만 보장, 서비스 준비 완료 보장 아님) | - database- redis |
| networks | 서비스를 연결할 사용자 정의 네트워크를 지정합니다. | - app-network |
| command | 컨테이너가 시작될 때 실행할 명령어를 재정의합니다. | command: npm run dev |
네트워크 및 볼륨 설정 전략
Docker Compose에서 네트워크와 볼륨은 다중 서비스 환경의 핵심입니다.
- 네트워크: Docker Compose는 기본적으로 하나의 프로젝트에 모든 서비스를 연결하는 기본 네트워크를 생성합니다. 하지만 명시적으로 사용자 정의 네트워크를 정의하면, 서비스 간의 통신을 더 명확하게 제어하고 격리할 수 있습니다. 예를 들어, 프론트엔드와 백엔드 API만 연결되는 네트워크, 백엔드와 데이터베이스만 연결되는 네트워크를 분리하여 보안을 강화할 수 있습니다. 컨테이너들은 네트워크 내에서 서비스 이름(예:
database)으로 서로 통신할 수 있습니다. - 볼륨: 컨테이너는 기본적으로 휘발성입니다. 컨테이너가 삭제되면 그 안에 저장된 데이터도 함께 사라집니다. 이를 방지하기 위해 볼륨을 사용합니다.
- 바인드 마운트 (Bind Mount): 호스트 머신의 특정 경로를 컨테이너 내부에 마운트합니다. 개발 중 소스 코드를 실시간으로 컨테이너에 반영하여 핫 리로드 기능을 활용할 때 유용합니다.
- Docker 관리 볼륨 (Named Volume): Docker가 직접 관리하는 볼륨입니다. 데이터베이스 데이터처럼 컨테이너의 생명 주기와 독립적으로 데이터를 유지해야 할 때 주로 사용됩니다.
이러한 설정들을 적절히 활용하면 견고하고 유연한 로컬 개발 환경을 구축할 수 있습니다.
실전 예제로 로컬 개발 환경 구축하기
이제 실제 예제를 통해 Docker Compose를 활용한 로컬 다중 서비스 개발 환경을 구축해 보겠습니다. 여기서는 간단한 웹 애플리케이션 스택을 예시로 듭니다.
- 백엔드: Node.js (Express 프레임워크 사용)
- 데이터베이스: PostgreSQL
- 관리 도구: pgAdmin (PostgreSQL 관리 웹 인터페이스)
프로젝트 구조는 다음과 같습니다.
.
├── docker-compose.yml
├── backend/
│ ├── Dockerfile
│ └── src/
│ └── app.js
└── .env # 환경 변수 파일
1단계: 백엔드 서비스 Dockerfile 작성
backend/Dockerfile 파일을 생성하고 다음 내용을 추가합니다.
# backend/Dockerfile
FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
backend/src/app.js 파일을 생성하고 간단한 Express 서버 코드를 추가합니다.
// backend/src/app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.POSTGRES_USER,
host: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DB,
password: process.env.POSTGRES_PASSWORD,
port: process.env.POSTGRES_PORT,
});
app.get('/', (req, res) => {
res.send('Hello from Node.js Backend!');
});
app.get('/db-test', async (req, res) => {
try {
const client = await pool.connect();
const result = await client.query('SELECT NOW()');
client.release();
res.send(`Database connected! Current time: ${result.rows[0].now}`);
} catch (err) {
console.error('Database connection error', err);
res.status(500).send('Database connection failed.');
}
});
app.listen(port, () => {
console.log(`Backend service listening at http://localhost:${port}`);
});
backend/package.json 파일도 생성합니다.
// backend/package.json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
}
}
Image by 2427999 on Pixabay
2단계: .env 파일 생성
프로젝트 루트에 .env 파일을 생성하고 데이터베이스 접속 정보를 정의합니다.
# .env
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
POSTGRES_PORT=5432
POSTGRES_HOST=db # db 서비스 이름을 호스트로 사용
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=adminpassword
3단계: docker-compose.yml 작성
프로젝트 루트에 docker-compose.yml 파일을 생성하고 다음 내용을 추가합니다.
# docker-compose.yml
version: '3.8'
services:
# 백엔드 서비스
backend:
build:
context: ./backend # Dockerfile이 있는 경로
ports:
- "3000:3000" # 호스트 3000번 포트를 컨테이너 3000번 포트에 연결
environment:
# .env 파일에서 환경 변수를 자동으로 로드
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_HOST=db # 데이터베이스 서비스 이름을 호스트로 사용
volumes:
- ./backend:/usr/src/app # 소스 코드 변경 시 즉시 반영
- /usr/src/app/node_modules # node_modules는 호스트에 마운트하지 않음 (컨테이너 내부만 사용)
depends_on:
- db # db 서비스가 먼저 시작되도록 의존성 설정
networks:
- app-network
# PostgreSQL 데이터베이스 서비스
db:
image: postgres:14-alpine # PostgreSQL 이미지 사용
environment:
# .env 파일에서 환경 변수를 로드하여 데이터베이스 초기 설정
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- db_data:/var/lib/postgresql/data # 데이터 영구 저장을 위한 볼륨
networks:
- app-network
# ports:
# - "5432:5432" # 로컬에서 직접 접근해야 할 경우 주석 해제
# pgAdmin (PostgreSQL 관리 도구) 서비스
pgadmin:
image: dpage/pgadmin4
environment:
- PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL}
- PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
ports:
- "5050:80" # 호스트 5050번 포트를 pgAdmin 컨테이너 80번 포트에 연결
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
- db # db 서비스가 먼저 시작되도록 의존성 설정
networks:
- app-network
# 사용자 정의 네트워크 정의
networks:
app-network:
driver: bridge
# 볼륨 정의
volumes:
db_data: # PostgreSQL 데이터 저장용 볼륨
pgadmin_data: # pgAdmin 설정 및 데이터 저장용 볼륨
4단계: 서비스 실행 및 테스트
프로젝트 루트 디렉토리에서 터미널을 열고 다음 명령어를 실행하여 모든 서비스를 백그라운드에서 실행합니다.
docker compose up -d
모든 컨테이너가 정상적으로 실행되는지 확인합니다.
docker compose ps
이제 웹 브라우저에서 다음 주소로 접속하여 테스트합니다.
- 백엔드 서비스: http://localhost:3000 (
Hello from Node.js Backend!메시지 확인) - 데이터베이스 연결 테스트: http://localhost:3000/db-test (데이터베이스 연결 성공 메시지 확인)
- pgAdmin: http://localhost:5050 (
.env파일에 설정한 이메일과 비밀번호로 로그인 후 PostgreSQL 서버에 연결)
개발 중 소스 코드를 수정하면, volumes 설정 덕분에 Node.js 서버가 자동으로 재시작되어 변경 사항이 즉시 반영될 것입니다.
모든 작업을 마치고 서비스를 종료하려면 다음 명령어를 사용합니다.
docker compose down
이 명령어는 모든 컨테이너를 중지하고 삭제하며, 기본 네트워크도 제거합니다. -v 옵션을 추가하면 Docker 볼륨(db_data, pgadmin_data)까지 함께 삭제되어 깨끗한 상태로 돌아갑니다.
docker compose down -v
더 효율적인 개발을 위한 팁
Docker Compose를 로컬 개발에 더욱 효과적으로 활용하기 위한 몇 가지 추가 팁입니다.
Image by yeiferr on Pixabay
환경 변수 관리 (.env 파일 활용)
민감한 정보(API 키, 데이터베이스 비밀번호 등)나 환경별로 달라지는 설정값들은 환경 변수로 관리하는 것이 좋습니다. Docker Compose는 .env 파일을 자동으로 로드하여 docker-compose.yml 파일 내에서 ${VAR_NAME} 형식으로 사용할 수 있게 해줍니다. 이 .env 파일은 Git 저장소에 커밋하지 않도록 .gitignore에 추가해야 합니다.
# .gitignore
.env
핫 리로드 (Hot Reloading) 설정
프론트엔드나 백엔드 서비스 개발 시, 코드 변경 사항이 저장될 때마다 컨테이너 내부의 애플리케이션이 자동으로 재시작되도록 핫 리로드를 설정하면 개발 생산성을 크게 높일 수 있습니다. 이는 volumes 설정을 통해 호스트의 소스 코드를 컨테이너에 마운트하고, 애플리케이션 내부에서 파일 변경을 감지하는 도구(예: Node.js의 nodemon, Python의 watchdog)를 사용하는 방식으로 구현됩니다.
위 예시의 Node.js 백엔드 서비스에서 npm start 대신 nodemon src/app.js를 실행하도록 package.json을 수정하고 nodemon을 설치하면 핫 리로드를 적용할 수 있습니다.
# backend/package.json (수정)
{
...
"scripts": {
"start": "nodemon src/app.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
},
"devDependencies": {
"nodemon": "^3.0.1" # 개발 의존성 추가
}
}
# backend/Dockerfile (수정)
...
RUN npm install
RUN npm install nodemon # nodemon 설치
COPY . .
EXPOSE 3000
CMD ["npm", "start"] # package.json의 start 스크립트 실행
다양한 환경별 설정 (-f 옵션 활용)
개발 환경, 테스트 환경, 프로덕션 환경 등 각기 다른 설정을 사용해야 할 때가 있습니다. 이 경우 여러 개의 docker-compose 파일을 만들어 관리할 수 있습니다.
docker-compose.yml: 기본 공통 설정docker-compose.dev.yml: 개발 환경에 특화된 설정 (예: 핫 리로드, 디버깅 포트 추가)docker-compose.prod.yml: 프로덕션 환경에 특화된 설정 (예: 볼륨 설정, 리소스 제한)
특정 파일을 사용하여 서비스를 실행할 때는 -f 옵션을 사용합니다.
# 기본 설정 + 개발 설정으로 서비스 실행
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# 기본 설정 + 프로덕션 설정으로 서비스 실행
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
이 방식은 설정의 재사용성을 높이고, 환경별 차이를 명확하게 관리할 수 있게 해줍니다.
마무리하며: 생산성 향상의 시작
이 글을 통해 Docker Compose를 활용하여 복잡한 로컬 다중 서비스 개발 환경을 어떻게 효율적으로 구축하고 관리할 수 있는지 알아보았습니다. 기존의 수동적인 환경 설정 방식과 비교했을 때, Docker Compose는 다음과 같은 혁신적인 변화를 가져다줍니다.
- 환경 설정에 소요되는 시간을 획기적으로 단축하여 핵심 개발에 집중할 수 있게 합니다.
- 모든 개발자가 동일한 환경에서 작업하므로 "내 컴퓨터에서는 잘 되는데..."와 같은 문제를 최소화합니다.
- 새로운 팀원이 프로젝트에 합류했을 때, 몇 분 안에 완벽한 개발 환경을 구축할 수 있도록 돕습니다.
- 각 서비스를 격리된 컨테이너에서 실행하여 종속성 충돌이나 포트 충돌 없이 안정적인 개발을 가능하게 합니다.
이제 여러분은 더 이상 복잡한 환경 설정으로 고민하지 않고, 오로지 코드 작성과 기능 구현에만 몰두할 수 있게 되었습니다. Docker Compose는 단순한 도구가 아니라, 개발자의 생산성과 팀의 협업 효율성을 극대화하는 강력한 솔루션입니다.
이 가이드가 여러분의 개발 여정에 큰 도움이 되었기를 바랍니다. Docker Compose를 활용한 경험이나 추가적인 팁이 있다면 댓글로 공유해 주세요. 여러분의 지식이 다른 개발자들에게도 큰 힘이 될 것입니다!
📌 함께 읽으면 좋은 글
- [튜토리얼] Docker Compose 활용 다중 서비스 로컬 개발 환경 구축 실전 가이드
- [튜토리얼] Node.js Apollo Server GraphQL API 구축: 개발자를 위한 실전 가이드
- [이슈 분석] 원격 및 하이브리드 근무 환경, 개발팀 협업 문화와 생산성 변화 심층 분석
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| GitHub Actions로 CI/CD 파이프라인 구축: 배포 자동화 실전 가이드 (1) | 2026.06.30 |
|---|---|
| FastAPI를 활용한 고성능 RESTful API 개발 및 배포 가이드 (0) | 2026.06.30 |
| Playwright E2E 테스트 자동화 완벽 가이드: 안정적인 웹 애플리케이션 구축 (0) | 2026.06.28 |
| Nx 모노레포 실전 가이드: 풀스택 TypeScript 개발 효율 높이기 (0) | 2026.06.28 |
| Docker Compose 활용 다중 서비스 로컬 개발 환경 구축 실전 가이드 (0) | 2026.06.27 |