튜토리얼

Docker Compose 다중 서비스 로컬 개발 환경 구축: 복잡함은 이제 그만!

강코의 코딩 일기 2026. 5. 20. 15:30
반응형

Docker Compose를 활용해 복잡한 다중 서비스 로컬 개발 환경을 쉽고 효율적으로 구축하는 방법을 배우세요. 컨테이너 기반 개발의 핵심을 실전 예제로 익혀보세요.

📑 목차

Docker Compose를 활용한 다중 서비스 로컬 개발 환경 구축 실전 가이드 - belgium, antwerp, shipping, container, freight, cargo, transport, harbor, container, container, container, freight, cargo, cargo, cargo, cargo, cargo

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})
  • ))}
Docker Compose를 활용한 다중 서비스 로컬 개발 환경 구축 실전 가이드 - candies, sweetmeats, jar, glass jar, container, glass container, confections, confectionery, treats, nibble, sweets, dessert, food, colorful, multicolored, delicious, sugar, candies, candies, jar, sweets, sweets, sweets, sweets, sweets, dessert, food, food, food, sugar, sugar, sugar

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` 파일에 민감한 정보(비밀번호 등)를 직접 노출하지 않고 안전하게 관리할 수 있답니다!

Docker Compose를 활용한 다중 서비스 로컬 개발 환경 구축 실전 가이드 - statue, sculpture, iron, steel, docker, finland, hamina, docker, docker, docker, docker, docker, finland

Image by Olga_Fil on Pixabay

다중 서비스 개발 환경 실행 및 관리

이제 모든 준비가 끝났어요! `docker-compose.yml` 파일이 있는 `my-fullstack-app` 디렉토리로 이동하여 다음 명령어를 실행하면 됩니다.

docker compose up -d

이 명령어는 다음 작업을 수행합니다.

  1. `backend`와 `frontend` 서비스의 `Dockerfile`을 기반으로 이미지를 빌드합니다 (처음 실행 시).
  2. `db` 서비스에 사용할 `postgres:13-alpine` 이미지를 다운로드합니다 (처음 실행 시).
  3. `app-network`라는 브릿지 네트워크를 생성합니다.
  4. `db_data`라는 볼륨을 생성합니다.
  5. `db`, `backend`, `frontend` 서비스 컨테이너를 정의된 순서(`depends_on`에 따라)대로 시작합니다.
  6. `-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 파이프라인 구축 실전 가이드

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.

반응형