토. 8월 16th, 2025

G: 안녕하세요! 복잡한 개발 환경에 지치신 여러분들을 위해, 오늘은 ✨Docker Compose✨라는 마법 같은 도구를 소개해드리려 합니다. 여러 개의 Docker 컨테이너를 하나하나 수동으로 관리하는 번거로움, 이제 그만! Docker Compose가 여러분의 워크플로우를 혁신적으로 바꿔줄 거예요.

이 글을 통해 여러분은 다음을 얻어가실 수 있습니다:

  • 🤔 Docker Compose가 왜 필요한지 이해하기
  • 💡 Docker Compose의 핵심 개념들을 마스터하기
  • 🛠️ 실제 웹 애플리케이션 스택을 예시로 직접 배포해보기
  • 🚀 Docker Compose를 더 스마트하게 활용하는 팁 얻기

자, 그럼 시작해볼까요? 레디~ 액션! 🎬


1. 🤔 Docker Compose, 왜 필요할까요?

여러분은 혹시 이런 경험을 해보셨나요?

docker run -d --name my-mongo -p 27017:27017 mongo:latest
docker run -d --name my-backend --link my-mongo:mongo -p 8080:8080 my-backend-image
docker run -d --name my-frontend -p 3000:3000 --link my-backend:backend my-frontend-image

수많은 docker run 명령어와 -p, --link, --network 등의 옵션들… 😵‍💫 이렇게 여러 개의 컨테이너가 복잡하게 엮여있는 애플리케이션을 개발하고 관리하다 보면, 머리가 지끈거리고 실수가 잦아지기 마련입니다. 특히 팀원들과 협업할 때는 “내 컴퓨터에서는 되는데 너 컴퓨터에서는 왜 안 돼?”라는 헬게이트가 열리기도 하죠. 🤯

Docker Compose는 바로 이런 문제들을 해결하기 위해 등장했습니다. Docker Compose는 여러 개의 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구입니다. 복잡한 컨테이너들의 설정(이미지, 포트, 볼륨, 네트워크, 환경 변수 등)을 docker-compose.yml이라는 하나의 YAML 파일에 명시하여, 단 하나의 명령어(docker compose up)로 모든 컨테이너를 한 번에 띄울 수 있게 해줍니다.

💡 Docker Compose 사용의 핵심 이점:

  • ⚡️ 간결함: 모든 설정을 하나의 파일에 집중시켜 관리합니다.
  • 🔁 재현성: 어떤 환경에서든 동일한 개발 및 배포 환경을 쉽게 구축할 수 있습니다.
  • 🤝 협업: 팀원들과 같은 설정 파일을 공유하여 개발 환경 동기화를 손쉽게 합니다.
  • 🔄 일관된 워크플로우: 개발, 테스트, 배포 환경 간의 차이를 최소화합니다.
  • 👍 편리한 관리: 컨테이너 시작, 중지, 재시작, 삭제 등이 훨씬 간편해집니다.

한 마디로, Docker Compose는 여러분의 멀티 컨테이너 환경을 매우 우아하고 효율적으로 정리해주는 ‘정리 마스터’라고 할 수 있습니다! ✨


2. 💡 Docker Compose 핵심 개념 완전 정복!

Docker Compose는 docker-compose.yml이라는 YAML 파일을 통해 작동합니다. 이 파일 안에 우리가 필요한 모든 설정을 정의하게 됩니다. 어떤 내용들이 들어가는지 주요 개념들을 살펴볼까요?

2.1. docker-compose.yml: 마법의 설계도 🗺️

모든 Docker Compose 프로젝트의 심장입니다. 이 파일은 YAML 문법으로 작성되며, 서비스(컨테이너), 네트워크, 볼륨 등을 정의합니다.

# docker-compose.yml 파일의 예시 구조
version: '3.8' # Docker Compose 파일 형식의 버전 (최신 버전을 사용하는 것이 좋습니다)

services:    # 이 섹션에 애플리케이션을 구성하는 각 컨테이너를 정의합니다.
  web:       # 서비스 이름 (컨테이너 이름과 다를 수 있지만, 기본적으로 컨테이너 이름이 됩니다)
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - api # 'api' 서비스가 먼저 시작되도록 의존성을 명시합니다.

  api:
    build: . # 현재 디렉토리의 Dockerfile을 사용하여 이미지를 빌드합니다.
    environment:
      - DB_HOST=database
      - DB_PORT=5432
    networks:
      - my-app-network # 이 서비스가 연결될 사용자 정의 네트워크

  database:
    image: postgres:13
    volumes:
      - db_data:/var/lib/postgresql/data # 데이터 영속성을 위한 볼륨

volumes:     # 데이터 볼륨을 정의합니다. (데이터 영속성 보장)
  db_data:

networks:    # 서비스 간 통신을 위한 네트워크를 정의합니다.
  my-app-network:
    driver: bridge # 기본적으로 bridge 드라이버를 사용합니다.

2.2. services: 애플리케이션의 핵심 구성 요소들 ⚙️

services는 Docker Compose 파일의 가장 중요한 부분입니다. 여기에 애플리케이션을 구성하는 각각의 컨테이너를 정의합니다. 각 서비스는 독립적인 컨테이너로 작동하며, 서로 통신할 수 있습니다.

  • image vs build:
    • image: 이미 Docker Hub 등에 미리 빌드되어 푸시된 이미지를 사용할 때 지정합니다.
      services:
        my-app:
          image: myuser/my-app:1.0 # Docker Hub 등에서 이미지를 가져옵니다.
    • build: 로컬 Dockerfile을 사용하여 직접 이미지를 빌드할 때 사용합니다. 일반적으로 개발 환경에서 많이 사용됩니다.
      services:
        my-app:
          build: . # 현재 디렉토리의 Dockerfile을 사용합니다.
          # build: ./backend # 특정 서브디렉토리의 Dockerfile을 사용할 수도 있습니다.
          context: ./backend # Dockerfile이 있는 경로
          dockerfile: Dockerfile # Dockerfile 이름 (기본은 Dockerfile)
  • ports: 호스트 머신의 포트와 컨테이너 내부 포트를 연결합니다. HOST_PORT:CONTAINER_PORT 형식입니다.
    services:
      web:
        ports:
          - "80:80"     # 호스트의 80번 포트를 컨테이너의 80번 포트에 연결
          - "443:443"   # SSL/TLS를 위한 443번 포트 연결
  • volumes: 컨테이너의 데이터를 호스트 머신에 영구적으로 저장하거나, 호스트의 파일을 컨테이너 내부에 마운트합니다. HOST_PATH:CONTAINER_PATH 또는 VOLUME_NAME:CONTAINER_PATH 형식입니다.
    services:
      db:
        volumes:
          - db_data:/var/lib/postgresql/data # 명명된 볼륨 (데이터 영속성에 필수!)
          - ./app/logs:/var/log/app # 호스트 경로 마운트 (로그 파일 등에 유용)
  • networks: 서비스 간 통신을 위한 네트워크를 지정합니다. 기본적으로 모든 서비스는 하나의 기본 네트워크에 연결되지만, 명시적으로 사용자 정의 네트워크를 지정하는 것이 좋습니다.
    services:
      frontend:
        networks:
          - my-app-net
      backend:
        networks:
          - my-app-net # 같은 네트워크에 속한 서비스끼리 이름으로 통신 가능!
  • environment: 컨테이너 내부에서 사용할 환경 변수를 설정합니다.
    services:
      backend:
        environment:
          - NODE_ENV=development
          - DB_HOST=database # 같은 네트워크 내의 다른 서비스 이름으로 접근 가능!
          - DB_PORT=5432
          # - "MY_SECRET=${ENV_VAR_FROM_HOST}" # 호스트 환경 변수를 가져올 수도 있습니다.
  • depends_on: 서비스 간의 의존성을 정의합니다. 명시된 서비스가 먼저 시작된 후에 해당 서비스가 시작됩니다. ⚠️ 주의: depends_on은 단순히 서비스 “시작 순서”만 보장하며, 해당 서비스가 “완전히 준비(ready)”되었음을 의미하지는 않습니다. 애플리케이션 로직에서 재시도 로직을 구현하거나, healthcheck를 함께 사용하는 것이 좋습니다.
    services:
      backend:
        depends_on:
          - database # database 서비스가 먼저 시작된 후 backend가 시작됩니다.
  • healthcheck: 컨테이너가 정상적으로 작동하는지 주기적으로 확인합니다. 이는 depends_on의 한계를 보완하여, 서비스가 “준비되었을” 때까지 기다리도록 할 수 있습니다. (Docker Compose v3.4 이상에서 depends_onhealthcheck를 결합하여 condition: service_healthy를 사용할 수 있습니다.)
    services:
      backend:
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
          interval: 30s
          timeout: 10s
          retries: 5
        depends_on:
          database:
            condition: service_healthy # database가 healthcheck를 통과해야 backend 시작

2.3. volumesnetworks: 데이터와 소통의 통로 🌉

docker-compose.yml 파일 최상단에서 전역적으로 volumesnetworks를 정의하여 서비스들이 공유하거나 사용할 수 있도록 합니다.

  • volumes:
    • 명명된 볼륨 (Named Volumes): 데이터를 컨테이너의 라이프사이클과 독립적으로 관리합니다. 컨테이너가 삭제되어도 데이터는 보존됩니다. 데이터베이스처럼 영속적인 데이터에 필수적입니다.
      volumes:
        db_data: # 'db_data'라는 이름의 볼륨 생성
    • 호스트 마운트 (Bind Mounts): 호스트 머신의 특정 경로를 컨테이너 내부에 마운트합니다. 주로 개발 환경에서 소스 코드 변경 사항을 즉시 반영하거나, 설정 파일을 공유할 때 사용됩니다.
      services:
        web:
          volumes:
            - ./my-app:/app # 호스트의 './my-app' 디렉토리를 컨테이너의 '/app'에 마운트
  • networks:
    • 사용자 정의 네트워크: 서비스 간의 격리된 통신 채널을 제공합니다. 같은 네트워크에 속한 서비스들은 서로의 서비스 이름으로 통신할 수 있습니다 (DNS Resolution).
      networks:
        app-tier: # 'app-tier'라는 이름의 네트워크 생성
          driver: bridge
        web-tier: # 'web-tier'라는 이름의 네트워크 생성
          driver: bridge

3. 🛠️ 실전 예제: 웹 애플리케이션 스택 배포하기

이제 실제로 웹 애플리케이션 스택을 Docker Compose로 배포해보겠습니다. 일반적인 시나리오로, Nginx (리버스 프록시/정적 파일 서빙) + Node.js (백엔드 API) + MongoDB (데이터베이스) 구성을 예로 들어볼게요.

3.1. 프로젝트 구조 만들기 📁

먼저, 프로젝트를 위한 디렉토리 구조를 만듭니다.

.
├── docker-compose.yml
├── backend/
│   ├── Dockerfile
│   └── app.js
├── nginx/
│   └── nginx.conf
└── .env # 환경 변수 파일 (선택 사항)

3.2. backend/Dockerfile 작성 📝

Node.js 백엔드 애플리케이션을 위한 Dockerfile을 작성합니다.

# backend/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "app.js"]

3.3. backend/app.js 작성 ✍️

간단한 Node.js Express 앱을 만들어 MongoDB 연결을 시도하고 API를 제공합니다.

// backend/app.js
const express = require('express');
const { MongoClient } = require('mongodb');
const app = express();
const port = 8080;

// 환경 변수에서 DB_HOST와 DB_PORT를 가져옵니다.
const dbHost = process.env.DB_HOST || 'localhost';
const dbPort = process.env.DB_PORT || '27017';
const dbName = 'mydatabase';
const mongoUri = `mongodb://${dbHost}:${dbPort}/${dbName}`;

let db;

async function connectToMongo() {
  try {
    const client = new MongoClient(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    db = client.db(dbName);
    console.log(`🎉 MongoDB connected successfully at ${mongoUri}`);
  } catch (err) {
    console.error(`💔 MongoDB connection failed: ${err.message}`);
    console.error(`Attempting to reconnect in 5 seconds...`);
    setTimeout(connectToMongo, 5000); // 5초 후 재시도
  }
}

connectToMongo(); // MongoDB 연결 시도

app.get('/', (req, res) => {
  res.send('Hello from Node.js Backend! 👋');
});

app.get('/status', (req, res) => {
  if (db) {
    res.json({ status: 'ok', message: 'MongoDB is connected!' });
  } else {
    res.status(503).json({ status: 'error', message: 'MongoDB is not connected yet.' });
  }
});

app.listen(port, () => {
  console.log(`Backend API listening on port ${port}`);
});

그리고 backend 디렉토리에 package.json 파일을 생성하고 expressmongodb 패키지를 추가합니다.

// backend/package.json
{
  "name": "my-backend",
  "version": "1.0.0",
  "description": "Node.js backend for multi-container app",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "mongodb": "^6.3.0"
  }
}

3.4. nginx/nginx.conf 작성 🌐

Nginx 설정 파일로, backend 서비스로 요청을 프록시합니다.

# nginx/nginx.conf
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html; # 정적 파일 서빙 (만약 frontend가 있다면 여기에)
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        location /api/ {
            proxy_pass http://api:8080/; # 'api'는 docker-compose.yml에 정의된 서비스 이름!
            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;
        }
    }
}

3.5. docker-compose.yml 작성 ✍️ (핵심!)

이제 이 모든 서비스를 연결하는 docker-compose.yml 파일을 작성합니다.

# docker-compose.yml
version: '3.8'

services:
  web:
    image: nginx:latest
    container_name: myapp-nginx
    ports:
      - "80:80" # 호스트의 80번 포트를 Nginx 컨테이너의 80번 포트에 연결
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Nginx 설정 파일 마운트 (읽기 전용)
      # - ./frontend/build:/usr/share/nginx/html # 만약 React/Vue 등 빌드된 프론트엔드가 있다면 여기 마운트
    depends_on:
      - api # api 서비스가 시작된 후 web 서비스 시작
    networks:
      - app-network

  api:
    build:
      context: ./backend # backend 디렉토리의 Dockerfile 사용
      dockerfile: Dockerfile
    container_name: myapp-api
    environment:
      # MongoDB 서비스 이름은 'mongo'로 접근 (Docker Compose 내부 DNS)
      DB_HOST: mongo 
      DB_PORT: 27017
    ports:
      - "8080:8080" # (선택 사항) 개발 중 백엔드 직접 접근을 위해 포트 오픈
    depends_on:
      mongo:
        condition: service_healthy # mongo가 건강 상태를 보고할 때까지 api 시작 대기
    networks:
      - app-network

  mongo:
    image: mongo:latest
    container_name: myapp-mongo
    ports:
      - "27017:27017" # (선택 사항) 호스트에서 MongoDB Compass 등으로 직접 접근을 위해 포트 오픈
    volumes:
      - mongo_data:/data/db # MongoDB 데이터 영속성을 위한 명명된 볼륨
    healthcheck: # MongoDB 건강 검사 정의
      test: echo 'db.runCommand("ping").ok' | mongosh --quiet # MongoDB 핑 테스트
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s # 초기 시작 시 건강 검사 시작 전 대기 시간
    networks:
      - app-network

networks: # 사용자 정의 네트워크 정의
  app-network:
    driver: bridge

volumes: # 명명된 볼륨 정의
  mongo_data:

3.6. .env 파일 (환경 변수) 🔑 (선택 사항)

민감한 정보나 자주 바뀌는 환경 변수는 .env 파일에 저장하고 docker-compose.yml에서 참조할 수 있습니다.

# .env 파일 (docker-compose.yml과 같은 디렉토리에 위치)
# DB_USERNAME=your_db_user
# DB_PASSWORD=your_db_password
# API_KEY=some_secret_key

(현재 예시에서는 직접 사용하지는 않았지만, 실제 프로젝트에서 유용하게 쓰일 수 있습니다.)


4. 🚀 Docker Compose 명령어 실행하기!

모든 파일 준비가 끝났으니, 이제 Docker Compose의 마법을 부려볼 시간입니다! docker-compose.yml 파일이 있는 디렉토리에서 다음 명령어를 실행합니다.

4.1. 모든 서비스 빌드 및 시작하기 (백그라운드 실행)

$ docker compose up -d 
  • -d (detach): 컨테이너를 백그라운드에서 실행하여 터미널을 다시 사용할 수 있게 합니다.
  • docker compose: Docker 엔진 20.10.0 버전부터 docker-compose 대신 docker compose 명령어를 사용할 수 있습니다. 기존 docker-compose 플러그인은 별도 설치가 필요했지만, 이제 Docker Desktop과 함께 기본으로 제공되는 통합 명령어입니다. 최신 사용법이므로 이를 따르는 것이 좋습니다.

4.2. 실행 중인 서비스 확인하기

$ docker compose ps

이 명령어를 실행하면, web, api, mongo 세 개의 컨테이너가 모두 Up 상태로 잘 실행되고 있는지 확인할 수 있습니다.

4.3. 컨테이너 로그 확인하기

$ docker compose logs -f

모든 서비스의 실시간 로그를 볼 수 있습니다. 특정 서비스의 로그만 보려면 서비스 이름을 지정합니다.

$ docker compose logs -f api # api 서비스의 로그만 보기

4.4. 서비스 테스트하기 웹 브라우저에서 http://localhost/api/status 에 접속해보세요. Nginx가 요청을 받아 api 서비스로 프록시하고, api 서비스는 MongoDB 연결 상태를 확인하여 JSON 응답을 줄 것입니다.

{"status":"ok","message":"MongoDB is connected!"}

만약 MongoDB 연결에 실패했다면, status: 'error'가 표시될 것입니다.

4.5. 특정 서비스 재시작하기

$ docker compose restart api

4.6. 특정 서비스 중지하기 (컨테이너는 남겨둠)

$ docker compose stop api

4.7. 모든 서비스 중지 및 컨테이너 삭제

$ docker compose down

이 명령은 모든 컨테이너, 네트워크를 중지하고 삭제합니다. volumes는 기본적으로 삭제되지 않으므로, 데이터가 보존됩니다.

4.8. 모든 서비스 중지, 컨테이너 및 볼륨까지 삭제

$ docker compose down -v

-v (volumes): 명명된 볼륨까지 함께 삭제합니다. 경고: 이 명령은 데이터베이스 데이터 등을 영구적으로 삭제하므로 주의해서 사용하세요! 🚨


5. 🚀 Docker Compose, 더 스마트하게 활용하기!

Docker Compose는 단순한 시작/중지 외에도 강력한 기능들을 제공합니다.

5.1. .env 파일로 환경 변수 관리 🔐

민감한 정보(API 키, 데이터베이스 비밀번호)나 환경에 따라 달라지는 값들은 .env 파일에 저장하고 docker-compose.yml에서 참조하는 것이 좋습니다.

# .env 파일 (docker-compose.yml과 같은 디렉토리)
DB_USER=myuser
DB_PASSWORD=mypassword
# docker-compose.yml
services:
  backend:
    environment:
      - DB_USER=${DB_USER} # .env 파일에서 DB_USER 값을 가져옴
      - DB_PASSWORD=${DB_PASSWORD}

5.2. 여러 Compose 파일 사용하기 (개발/운영 환경 분리) 🌳

개발 환경과 운영 환경의 설정이 다를 때, 여러 개의 Compose 파일을 오버라이드하여 사용할 수 있습니다. 예를 들어, docker-compose.yml (기본), docker-compose.dev.yml (개발), docker-compose.prod.yml (운영) 파일을 만들 수 있습니다.

  • docker-compose.yml (공통 설정)
  • docker-compose.dev.yml (개발 환경에서만 추가될 설정)
    # docker-compose.dev.yml
    services:
      api:
        build:
          context: ./backend
          dockerfile: Dockerfile.dev # 개발 전용 Dockerfile 사용
        volumes:
          - ./backend:/app # 소스 코드 실시간 반영을 위한 바인드 마운트
  • 실행 명령어:
    $ docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

    -f 옵션을 여러 번 사용하여 파일을 지정할 수 있으며, 뒤에 오는 파일이 앞 파일의 설정을 덮어씁니다.

5.3. 서비스 스케일링 (수평 확장) 📈

특정 서비스의 컨테이너 수를 늘려 부하를 분산할 수 있습니다.

$ docker compose up -d --scale api=3 # api 서비스를 3개의 컨테이너로 실행

5.4. profiles를 이용한 서비스 그룹화 (Compose v3.4 이상) 🏷️

프로젝트 내에 여러 개의 독립적인 애플리케이션 스택이 있거나, 특정 개발 시나리오에만 필요한 서비스들이 있을 때 profiles를 사용하여 서비스를 그룹화할 수 있습니다.

# docker-compose.yml 예시
version: '3.8'

services:
  web:
    image: nginx
    ports: ["80:80"]

  api:
    build: ./backend

  admin-tool:
    image: adminer/adminer # 관리자 툴
    ports: ["8081:8080"]
    profiles: ["admin"] # 'admin' 프로필이 활성화될 때만 실행

  qa-tester:
    image: cypress/included # QA 테스트용 컨테이너
    profiles: ["qa"] # 'qa' 프로필이 활성화될 때만 실행

networks:
  default:
  • 모든 서비스 실행 (profiles 제외): $ docker compose up -d (web, api만 실행)
  • 특정 프로필 활성화하여 실행: $ docker compose --profile admin up -d (web, api, admin-tool 실행) $ docker compose --profile qa up -d (web, api, qa-tester 실행)
  • 여러 프로필 동시 활성화: $ docker compose --profile admin --profile qa up -d (web, api, admin-tool, qa-tester 실행)

맺음말 ✨

지금까지 Docker Compose가 무엇인지, 왜 필요한지, 그리고 어떻게 사용하는지 자세히 알아보았습니다. 복잡한 멀티 컨테이너 환경을 Docker Compose로 한 방에 정리하는 방법을 익히셨기를 바랍니다!

Docker Compose는 특히 로컬 개발 환경 구축에 탁월한 성능을 발휘합니다. 개발 환경을 코드로 관리하고, 팀원들과의 협업을 원활하게 하며, “내 컴퓨터에서는 되는데…”와 같은 문제를 효과적으로 줄여줄 것입니다. 💖

물론, 프로덕션 환경에서 대규모 분산 시스템을 운영할 때는 Kubernetes와 같은 더 강력한 오케스트레이션 도구를 고려해야 합니다. 하지만 개발 및 테스트 환경에서는 Docker Compose만으로도 충분히 강력하고 유연한 환경을 구축할 수 있습니다.

이제 여러분의 개발 워크플로우를 Docker Compose로 업그레이드할 시간입니다! 🚀 궁금한 점이 있다면 언제든지 댓글로 남겨주세요. 다음에는 더 유익한 정보로 찾아뵙겠습니다! 해피 도커링! 🐳👋

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다