수. 8월 13th, 2025

G: 안녕하세요, 개발자 여러분! 👨‍💻👩‍💻 혹시 복잡한 애플리케이션을 개발하고 배포하면서, 여러 개의 컨테이너를 하나하나 수동으로 관리하는 데 지쳐본 적 없으신가요? 웹 서버, 데이터베이스, 캐시 서버, 메시지 큐 등 다양한 서비스가 얽혀 있는 상황에서 각 컨테이너를 띄우고, 연결하고, 순서를 맞추는 일은 여간 번거로운 일이 아닐 수 없습니다.

하지만 걱정 마세요! ✨ 오늘 저희는 바로 이런 문제를 해결해 줄 마법 같은 도구, Docker Compose를 깊이 파헤쳐 볼 것입니다. Docker Compose는 단순한 컨테이너 실행을 넘어, 여러 컨테이너로 구성된 복잡한 애플리케이션을 쉽고 효율적으로 관리할 수 있도록 돕는 서비스 오케스트레이션 도구입니다. 이 글을 통해 Docker Compose의 기본 개념부터 실제 활용 예시, 그리고 고급 기능까지 마스터하여 여러분의 개발 워크플로우를 한 단계 업그레이드할 수 있도록 도와드리겠습니다!


1. Docker Compose는 무엇인가요? 🤔

Docker Compose는 다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구입니다. 쉽게 말해, 여러 개의 독립적인 컨테이너들이 서로 유기적으로 연결되어 하나의 거대한 애플리케이션 서비스를 구성할 때, 이 컨테이너들을 마치 하나의 유닛처럼 관리할 수 있게 해주는 ‘지휘자’ 역할을 합니다.

이 모든 것은 docker-compose.yml이라는 단일 YAML 파일을 통해 이루어집니다. 이 파일 안에 애플리케이션을 구성하는 모든 서비스(컨테이너), 네트워크, 볼륨 등을 명시하고, 단 하나의 명령어로 전체 애플리케이션 스택을 한 번에 실행하거나 중지할 수 있습니다.

핵심 개념 요약:

  • YAML 파일: 애플리케이션 구성을 선언적으로 정의합니다.
  • 서비스: 각 컨테이너를 하나의 서비스로 정의합니다. (예: web, db, redis)
  • 네트워크: 서비스 간의 통신을 위한 가상 네트워크를 정의합니다.
  • 볼륨: 컨테이너의 데이터를 영구적으로 저장하고 공유하기 위한 공간을 정의합니다.

2. 왜 Docker Compose를 사용해야 할까요? ✨

Docker Compose를 사용하면 얻을 수 있는 이점은 다음과 같습니다.

  • ⚡️ 간편한 설정 및 실행: 복잡한 docker run 명령어를 일일이 입력할 필요 없이, docker-compose.yml 파일 하나로 모든 설정을 정의하고 docker compose up 명령어 한 줄로 전체 애플리케이션을 실행할 수 있습니다.
  • 🔄 재현성(Reproducibility): 동일한 docker-compose.yml 파일만 있다면, 어떤 환경에서든 동일한 애플리케이션 스택을 정확하게 재현할 수 있습니다. 이는 팀 프로젝트나 개발 환경 설정 시 엄청난 이점입니다.
  • 🧑‍💻 로컬 개발 환경 최적화: 개발 시 필요한 모든 서비스(DB, 캐시, 메시지 큐 등)를 컨테이너로 띄워 로컬에서 실제 프로덕션 환경과 유사하게 테스트할 수 있습니다.
  • 🌐 서비스 간의 쉬운 통신: Compose는 서비스 간의 네트워크를 자동으로 설정하여, 컨테이너 이름으로 쉽게 서로를 호출할 수 있게 해줍니다. (예: 웹 서버에서 데이터베이스를 db라는 이름으로 접근)
  • 🔄 버전 관리 용이: 애플리케이션의 인프라 설정을 코드(YAML)로 관리하므로 Git과 같은 버전 관리 시스템으로 쉽게 추적하고 협업할 수 있습니다.

3. Docker Compose 핵심 문법 파헤치기 🧩

docker-compose.yml 파일은 Docker Compose의 심장입니다. 이 파일의 주요 섹션들을 자세히 살펴보겠습니다.

3.1. version (버전 명시)

파일의 첫 줄에는 version을 명시합니다. 이는 Docker Compose 파일 형식의 API 버전을 나타냅니다. 최신 버전은 보통 3.x대를 사용합니다.

version: '3.8' # 최신 기능을 사용하기 위해 높은 버전을 사용하는 것이 좋습니다.

3.2. services (서비스 정의)

services 섹션은 애플리케이션을 구성하는 개별 컨테이너(서비스)들을 정의하는 곳입니다. 각 서비스는 독립적인 컨테이너 인스턴스를 나타냅니다.

services:
  web: # 'web'이라는 서비스 정의 시작
    build: . # 현재 디렉토리의 Dockerfile을 사용하여 이미지를 빌드합니다.
    # image: 'nginx:latest' # 이미 빌드된 이미지를 사용할 경우 (둘 중 하나 선택)
    ports: # 호스트 포트:컨테이너 포트
      - "80:80"
    volumes: # 호스트 경로:컨테이너 경로 (데이터 지속성 및 코드 마운트)
      - ./nginx.conf:/etc/nginx/nginx.conf # 설정 파일 마운트
      - .:/app # 현재 디렉토리의 코드를 컨테이너의 /app 디렉토리에 마운트
    environment: # 환경 변수 설정
      NODE_ENV: production
      PORT: 80
    depends_on: # 이 서비스가 시작되기 전에 의존하는 서비스를 명시 (순서 보장)
      - db
      - redis
    networks: # 이 서비스가 연결될 네트워크 (선택 사항, 없으면 기본 네트워크 사용)
      - app-network
    restart: always # 컨테이너 종료 시 항상 재시작

  db: # 'db'라는 서비스 정의 시작
    image: 'postgres:13'
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db-data:/var/lib/postgresql/data # named volume을 사용하여 데이터 지속성 확보
    networks:
      - app-network

  redis: # 'redis'라는 서비스 정의 시작
    image: 'redis:latest'
    networks:
      - app-network

주요 속성:

  • build: Dockerfile이 있는 경로를 지정하여 이미지를 빌드합니다.
  • image: Docker Hub나 다른 레지스트리에 있는 이미지를 사용합니다.
  • ports: 호스트 머신의 포트와 컨테이너 내부의 포트를 연결합니다. 호스트_포트:컨테이너_포트 형식입니다.
  • volumes: 데이터를 영구적으로 저장하거나, 호스트의 파일을 컨테이너 내부에 마운트합니다.
    • ./host_path:/container_path: 바인드 마운트 (호스트의 특정 경로와 연결)
    • volume_name:/container_path: 명명된 볼륨 (Docker가 관리하는 볼륨과 연결)
  • environment: 컨테이너 내부에 전달할 환경 변수를 설정합니다.
  • depends_on: 서비스 간의 시작 순서를 정의합니다. db가 시작된 후 web이 시작되도록 할 때 유용합니다. (단, 서비스의 준비 완료를 의미하는 것은 아님)
  • networks: 서비스가 연결될 네트워크를 명시합니다.
  • restart: 컨테이너가 종료되었을 때 재시작 정책을 설정합니다. (no, on-failure, always, unless-stopped)

3.3. volumes (볼륨 정의)

volumes 섹션은 services 섹션에서 사용할 명명된 볼륨을 정의하는 곳입니다. 데이터를 컨테이너 외부에서 영구적으로 관리할 때 사용됩니다.

volumes:
  db-data: # 'db-data'라는 이름의 볼륨 정의
  cache-data: # 'cache-data'라는 이름의 볼륨 정의

3.4. networks (네트워크 정의)

networks 섹션은 서비스 간의 통신을 위한 사용자 정의 네트워크를 정의합니다. 기본적으로 Docker Compose는 모든 서비스를 하나의 default 브릿지 네트워크에 연결하지만, 명시적으로 네트워크를 정의하면 더 명확하고 안전한 통신 환경을 구축할 수 있습니다.

networks:
  app-network: # 'app-network'라는 이름의 네트워크 정의
    driver: bridge # 네트워크 드라이버 (기본값은 bridge)

4. 실전 예제: 간단한 웹 애플리케이션 스택 배포하기 🚀

이제 실제로 docker-compose.yml 파일을 작성하여 Node.js 웹 애플리케이션, PostgreSQL 데이터베이스, 그리고 Redis 캐시 서버로 구성된 간단한 스택을 배포해 봅시다.

시나리오:

  • Web: Node.js 기반의 API 서버 (포트 3000)
  • DB: PostgreSQL 데이터베이스
  • Redis: Redis 캐시 서버

파일 구조:

my-app/
├── server.js
├── Dockerfile
└── docker-compose.yml

server.js (간단한 Node.js 서버 예시):

// 이 파일은 실제 동작하는 서버가 아니라, Compose 예시를 위한 가상의 파일입니다.
const express = require('express');
const { Pool } = require('pg');
const redis = require('redis');

const app = express();
const port = process.env.PORT || 3000;

// PostgreSQL 연결 설정 (Docker Compose 서비스 이름 'db' 사용)
const pgPool = new Pool({
  user: process.env.POSTGRES_USER || 'user',
  host: process.env.DB_HOST || 'db', // <- Docker Compose 서비스 이름!
  database: process.env.POSTGRES_DB || 'mydatabase',
  password: process.env.POSTGRES_PASSWORD || 'password',
  port: 5432,
});

// Redis 연결 설정 (Docker Compose 서비스 이름 'redis' 사용)
const redisClient = redis.createClient({
  host: process.env.REDIS_HOST || 'redis', // <- Docker Compose 서비스 이름!
  port: 6379,
});

redisClient.on('connect', () => console.log('Redis connected!'));
redisClient.on('error', (err) => console.error('Redis error:', err));

app.get('/', async (req, res) => {
  try {
    // DB에서 데이터 조회 예시
    const client = await pgPool.connect();
    const result = await client.query('SELECT NOW()');
    client.release();
    const dbTime = result.rows[0].now;

    // Redis에 데이터 저장/조회 예시
    await redisClient.set('mykey', 'Hello from Redis!');
    const redisValue = await redisClient.get('mykey');

    res.send(`

<h1>Hello from Docker Compose!</h1>

<p>Database Time: ${dbTime}</p>

<p>Redis Value: ${redisValue}</p>

<p>Server running on port ${port}</p>
    `);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).send('Internal Server Error. Check server logs.');
  }
});

app.listen(port, () => {
  console.log(`Web server running on http://localhost:${port}`);
});

Dockerfile (Node.js 앱을 위한):

FROM node:18-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

docker-compose.yml:

version: '3.8'

services:
  web:
    build: . # 현재 디렉토리의 Dockerfile을 사용하여 웹 서비스 이미지 빌드
    ports:
      - "3000:3000" # 호스트의 3000번 포트를 컨테이너의 3000번 포트에 연결
    environment: # 환경 변수 설정
      PORT: 3000
      DB_HOST: db # 데이터베이스 서비스의 호스트 이름은 'db'
      POSTGRES_USER: user
      POSTGRES_DB: mydatabase
      POSTGRES_PASSWORD: password
      REDIS_HOST: redis # Redis 서비스의 호스트 이름은 'redis'
    depends_on: # web 서비스는 db와 redis가 시작된 후에 시작
      - db
      - redis
    networks: # 모든 서비스는 'app-network'에 연결
      - app-network
    restart: on-failure # 컨테이너가 실패하면 재시작

  db:
    image: postgres:13 # PostgreSQL 13 버전 이미지 사용
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data # 데이터베이스 데이터를 위한 명명된 볼륨 사용
    networks:
      - app-network
    restart: always

  redis:
    image: redis:latest # 최신 Redis 이미지 사용
    networks:
      - app-network
    restart: always

volumes:
  db_data: # 'db_data' 명명된 볼륨 정의 (PostgreSQL 데이터 보존용)

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

4.1. Docker Compose 명령어 실행 🚀

docker-compose.yml 파일이 있는 디렉토리에서 터미널을 열고 다음 명령어를 실행합니다.

  1. 애플리케이션 스택 빌드 및 실행 (백그라운드 모드):

    docker compose up -d
    • -d (detached mode): 컨테이너를 백그라운드에서 실행하여 터미널을 점유하지 않습니다.
    • docker-compose 대신 docker compose (하이픈 없이)를 사용하는 것이 최신 Docker CLI의 권장 방식입니다. (docker-compose 플러그인이 설치되어 있어야 합니다.)
  2. 실행 중인 서비스 확인:

    docker compose ps

    👉 모든 서비스(web, db, redis)가 Up 상태인지 확인하세요!

  3. 서비스 로그 확인:

    docker compose logs -f
    • -f (follow): 실시간으로 모든 서비스의 로그를 확인할 수 있습니다.
    • 특정 서비스의 로그만 보고 싶다면: docker compose logs web
  4. 웹 애플리케이션 접속: 이제 웹 브라우저를 열고 http://localhost:3000으로 접속해 보세요. 🌐 위에서 작성한 Node.js 서버의 응답을 볼 수 있을 것입니다!

  5. 서비스 중지 및 제거: 모든 서비스를 중지하고 관련 컨테이너, 네트워크, 볼륨(명명된 볼륨 포함)을 제거하려면:

    docker compose down --volumes
    • --volumes (또는 -v): 명명된 볼륨도 함께 제거합니다. 데이터베이스 데이터가 유실될 수 있으니 주의하세요!
    • 컨테이너만 중지하고 제거하고 싶다면 --volumes 없이 docker compose down만 사용하면 됩니다.
  6. 특정 서비스만 시작/중지:

    docker compose up -d web # web 서비스만 시작 (의존성 포함)
    docker compose stop db # db 서비스만 중지

5. Docker Compose 고급 기능 살펴보기 💡

Docker Compose는 단순한 실행을 넘어 더 유연하고 강력한 애플리케이션 관리를 위한 고급 기능들을 제공합니다.

5.1. 여러 Compose 파일 사용하기 (-f)

개발, 테스트, 프로덕션 등 환경별로 설정을 다르게 하고 싶을 때 유용합니다. 예를 들어, docker-compose.yml은 기본 설정을 담고, docker-compose.dev.yml은 개발 환경에 특화된 설정을, docker-compose.prod.yml은 프로덕션 설정을 담을 수 있습니다.

# docker-compose.yml (기본 설정)
version: '3.8'
services:
  web:
    build: .
    networks:
      - app-network
  db:
    image: postgres:13
    networks:
      - app-network
networks:
  app-network:
# docker-compose.dev.yml (개발 환경 설정)
version: '3.8'
services:
  web:
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app # 개발 시 코드 변경이 바로 반영되도록 바인드 마운트
    environment:
      NODE_ENV: development
      DEBUG: 'true'
  db:
    ports:
      - "5432:5432" # 개발 중 외부 DB 클라이언트로 접속 가능하도록 포트 오픈

사용법:

  • 기본 설정 + 개발 설정: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
  • Compose는 여러 파일을 병합하여 최종 설정을 만듭니다. 나중에 명시된 파일의 설정이 우선권을 가집니다.

5.2. extends (설정 재사용)

여러 서비스에서 공통적으로 사용되는 설정을 별도의 파일로 분리하여 재사용할 수 있습니다.

# common-configs.yml
version: '3.8'
x-logging: &default-logging # 앵커 정의
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  _base: # 이 서비스는 직접 실행되지 않고 extends를 위한 베이스 역할
    networks:
      - app-network
    logging: *default-logging # 앵커 참조
# docker-compose.yml
version: '3.8'
services:
  web:
    extends: # common-configs.yml 파일의 _base 서비스를 확장
      file: common-configs.yml
      service: _base
    build: .
    ports:
      - "3000:3000"
  db:
    extends:
      file: common-configs.yml
      service: _base
    image: postgres:13
networks:
  app-network:

5.3. profiles (선택적 서비스 실행)

특정 환경이나 작업 흐름에 따라 일부 서비스만 선택적으로 실행하고 싶을 때 사용합니다.

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
  db:
    image: postgres:13
  adminer: # 데이터베이스 GUI 툴 (개발/관리 용도)
    image: adminer
    ports:
      - "8080:8080"
    profiles: ["debug"] # 'debug' 프로파일이 활성화될 때만 실행
    networks:
      - default

사용법:

  • 기본 서비스만 실행: docker compose up -d
  • adminer를 포함한 모든 서비스 실행: docker compose --profile debug up -d

5.4. healthcheck (서비스 상태 점검)

컨테이너가 단순히 실행 중인 것을 넘어, 애플리케이션 내부적으로 “준비 완료” 상태인지 확인할 때 사용합니다. depends_on은 컨테이너가 시작되는 순서만 보장하지만, healthcheck는 실제 서비스가 요청을 처리할 준비가 되었는지 확인합니다.

services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy # db 서비스가 healthy 상태가 될 때까지 기다림
      redis:
        condition: service_started # redis는 시작만 되면 됨
    healthcheck: # web 서비스의 헬스체크 정의
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"] # 헬스체크 명령어
      interval: 10s # 10초마다 체크
      timeout: 5s # 5초 안에 응답이 없으면 실패
      retries: 3 # 3회 실패 시 unhealthy 상태로 간주
      start_period: 20s # 처음 20초는 시작 기간으로, 실패해도 unhealthy로 간주하지 않음

5.5. 리소스 제한 (deploy.resources)

컨테이너가 사용할 수 있는 CPU, 메모리 등의 리소스를 제한할 수 있습니다. 특히 로컬 환경에서 컨테이너가 너무 많은 리소스를 소비하는 것을 방지할 때 유용합니다.

services:
  web:
    build: .
    deploy: # 배포 관련 설정 (Docker Swarm에서도 사용됨)
      resources:
        limits: # 최대 사용 제한
          cpus: '0.50' # 0.5 코어
          memory: 512M # 512MB
        reservations: # 보장되는 최소 리소스
          cpus: '0.25'
          memory: 256M

6. Docker Compose의 한계 및 언제 다른 도구를 고려해야 할까? 🧐

Docker Compose는 로컬 개발, 테스트, 그리고 소규모 애플리케이션 배포에 탁월한 도구입니다. 하지만 프로덕션 환경에서 대규모 분산 시스템을 운영하기에는 다음과 같은 한계가 있습니다.

  • 단일 호스트에 최적화: Docker Compose는 기본적으로 단일 서버(호스트) 내에서 컨테이너들을 관리하는 데 중점을 둡니다. 여러 서버에 걸쳐 서비스를 배포하고 관리하는 기능은 제공하지 않습니다.
  • 자동 스케일링 부재: 트래픽 증가에 따라 자동으로 컨테이너 인스턴스를 늘리거나 줄이는 기능이 없습니다. 수동으로 docker compose up --scale web=3 명령어를 사용해야 합니다.
  • 자가 치유(Self-healing) 및 롤링 업데이트 제한적: 컨테이너에 문제가 생겼을 때 자동으로 복구하거나, 서비스 중단 없이 새 버전을 배포하는 롤링 업데이트 기능이 부족합니다.
  • 복잡한 네트워크 및 스토리지 관리 부족: 대규모 분산 환경에서 필요한 고급 네트워크 정책이나 스토리지 솔루션 통합 기능이 제한적입니다.

이러한 한계를 넘어설 필요가 있을 때 고려할 수 있는 도구:

  • Docker Swarm: Docker 엔진에 내장된 오케스트레이션 도구입니다. Docker Compose와 유사한 문법을 사용하지만, 여러 서버(노드)를 묶어 클러스터를 구성하고 스케일링, 로드 밸런싱, 자가 치유 등의 기능을 제공합니다. Compose 파일을 Swarm 스택으로 배포할 수 있습니다.
  • Kubernetes (K8s): 현재 가장 널리 사용되는 컨테이너 오케스트레이션 플랫폼입니다. 매우 강력하고 유연하며, 대규모 분산 시스템 운영에 필요한 모든 고급 기능을 제공합니다. 학습 곡선이 높지만, 프로덕션 환경의 표준으로 자리 잡았습니다.

7. Docker Compose 활용 베스트 프랙티스 💡

Docker Compose를 더욱 효과적으로 사용하기 위한 몇 가지 팁입니다.

  • .env 파일 활용: 민감한 정보(비밀번호, API 키 등)는 docker-compose.yml 파일에 직접 노출하지 않고 .env 파일에 정의하여 관리하세요. Compose는 자동으로 이 파일을 읽어 환경 변수를 주입합니다.
    # .env 파일 예시
    POSTGRES_PASSWORD=my_secure_password
    REDIS_HOST=redis
  • Dockerfile과 Compose 분리: Dockerfile은 이미지 빌드에 집중하고, Compose 파일은 서비스 구성 및 컨테이너 간의 관계 정의에 집중하도록 역할을 분리하세요.
  • 명명된 볼륨 사용: 데이터베이스 등 영구적인 데이터가 필요한 서비스에는 바인드 마운트보다 명명된 볼륨을 사용하는 것을 권장합니다. 이는 데이터 관리 및 백업에 더 유리합니다.
  • 헬스체크 적극 활용: healthcheck를 정의하여 서비스의 실제 건강 상태를 확인하고, depends_on과 함께 사용하여 서비스 시작 순서를 더욱 견고하게 만드세요.
  • 작은 이미지 사용: 가능한 한 alpine 버전의 베이스 이미지를 사용하여 컨테이너 크기를 최소화하세요. 이는 빌드 및 배포 속도에 긍정적인 영향을 줍니다.
  • 자주 커밋하고 공유: docker-compose.yml 파일은 애플리케이션의 인프라 코드이므로, Git과 같은 버전 관리 시스템에 철저히 관리하고 팀원들과 공유하세요.

결론 🎉

지금까지 Docker Compose가 무엇인지부터 시작하여 핵심 문법, 실제 예제, 고급 기능, 그리고 한계점과 베스트 프랙티스까지 상세히 살펴보았습니다. Docker Compose는 여러 컨테이너로 구성된 복잡한 애플리케이션을 로컬에서 개발하고 테스트하며, 소규모 환경에서 배포하는 데 있어 강력하고 생산적인 도구임이 분명합니다.

이 도구를 마스터함으로써 여러분은 애플리케이션 개발 워크플로우를 크게 개선하고, 컨테이너 기반 환경을 더욱 효율적으로 다룰 수 있게 될 것입니다. 이제 더 이상 개별 컨테이너 관리의 번거로움에 시달리지 마세요! 😌 Docker Compose와 함께라면 여러분의 개발 여정이 더욱 부드럽고 즐거워질 것입니다.

오늘 배운 지식을 바탕으로 여러분의 다음 프로젝트에서 Docker Compose를 적극적으로 활용해 보세요. 궁금한 점이 있다면 언제든지 찾아보고 실험해 보는 것을 추천합니다. Happy Dockering! 🐳💖

답글 남기기

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