튜토리얼

Docker Compose로 로컬 개발 환경 완벽 구축: 다중 서비스 애플리케이션 설정 가이드

강코의 코딩 일기 2026. 4. 12. 16:20
반응형

Docker Compose를 활용하여 복잡한 다중 서비스 애플리케이션의 로컬 개발 환경을 효율적으로 구축하는 방법을 상세히 안내합니다. 컨테이너화된 환경에서 개발 생산성을 극대화하세요.

개발자라면 누구나 한 번쯤 로컬 개발 환경을 설정하면서 복잡한 의존성 문제, 버전 충돌, 그리고 "내 컴퓨터에서는 잘 되는데?"라는 팀원 간의 환경 불일치 문제에 직면해 보셨을 겁니다. 특히 여러 서비스가 유기적으로 연결된 다중 서비스 애플리케이션을 개발할 때는 이러한 복잡성이 더욱 증폭됩니다. 데이터베이스, 캐시, 메시지 큐, 그리고 여러 API 서버까지, 이 모든 것을 수동으로 설치하고 구성하는 것은 시간 소모적일 뿐만 아니라 오류 발생 가능성도 높습니다.

이 글에서는 이러한 문제들을 해결하고 개발 생산성을 혁신적으로 끌어올릴 수 있는 도구, 바로 Docker Compose를 활용한 로컬 개발 환경 구축 방법을 상세하게 다룹니다. 단 하나의 설정 파일로 여러 컨테이너 서비스를 정의하고 관리하여, 어떤 개발 환경에서든 일관되고 재현 가능한 개발 환경을 손쉽게 구축하는 노하우를 공유합니다. 이제 더 이상 환경 설정에 시간을 낭비하지 않고, 오직 코드 작성에만 집중할 수 있게 될 것입니다.

📑 목차

Docker Compose를 활용한 로컬 개발 환경 구축: 다중 서비스 애플리케이션 설정 가이드 - statue, sculpture, iron, steel, docker, finland, hamina, docker, docker, docker, docker, docker, finland

Image by Olga_Fil on Pixabay

로컬 개발 환경의 복잡성, Docker Compose가 답이다

다중 서비스 애플리케이션을 개발할 때 마주하는 가장 큰 난관 중 하나는 바로 로컬 개발 환경의 복잡성입니다. 프론트엔드 서비스, 백엔드 API, 데이터베이스, Redis, 메시지 큐 등 다양한 기술 스택이 얽혀있는 현대적인 애플리케이션은 각 서비스별로 특정 버전의 라이브러리나 런타임이 필요할 수 있습니다. 예를 들어, 한 서비스는 Node.js 16을, 다른 서비스는 Node.js 18을 요구할 수 있으며, 특정 데이터베이스 버전과의 호환성 문제도 빈번하게 발생합니다.

이러한 복잡성은 다음과 같은 문제들을 야기합니다:

  • 환경 불일치: 팀원마다 다른 운영체제나 라이브러리 버전을 사용하여 "내 컴퓨터에서는 잘 되는데 다른 사람 컴퓨터에서는 안 되는" 문제가 발생합니다.
  • 설정 시간 소모: 새로운 개발자가 온보딩할 때, 혹은 개발 환경을 재설정해야 할 때, 모든 구성 요소를 수동으로 설치하고 설정하는 데 엄청난 시간이 소요됩니다.
  • 의존성 지옥: 특정 라이브러리나 런타임의 버전을 올리거나 내릴 때, 다른 서비스에 미치는 영향을 예측하기 어렵고, 종종 예상치 못한 오류를 유발합니다.

Docker는 이러한 문제들을 해결하기 위한 강력한 컨테이너화 도구입니다. 애플리케이션과 그 모든 의존성을 격리된 환경인 컨테이너에 담아, 어디서든 동일하게 동작하도록 보장합니다. 하지만 단일 Docker 컨테이너만으로는 다중 서비스 애플리케이션의 복잡성을 완전히 해소하기 어렵습니다. 여러 컨테이너를 개별적으로 실행하고, 서로 연결하며, 볼륨을 관리하는 것은 여전히 많은 수동 작업과 명령어를 필요로 합니다.

여기서 등장하는 것이 바로 Docker Compose입니다. Docker Compose는 다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구입니다. YAML 파일을 사용하여 애플리케이션의 모든 서비스를 한 번에 구성하고, 단일 명령어로 전체 스택을 시작하거나 중지할 수 있습니다. 이를 통해 개발자는 환경 설정의 어려움에서 벗어나, 오직 핵심 비즈니스 로직 개발에만 집중할 수 있게 됩니다.

Docker Compose, 왜 필요한가? 핵심 장점 분석

Docker Compose가 로컬 개발 환경 구축에 필수적인 도구로 자리매김한 데에는 명확한 이유가 있습니다. 수동으로 Docker 명령어를 사용하는 것과 비교했을 때, Docker Compose는 여러 면에서 압도적인 장점을 제공합니다.

수동 Docker 명령어와의 비교

단일 서비스라면 docker run 명령어로 컨테이너를 시작하는 것이 간단할 수 있습니다. 하지만 서비스가 2개, 3개, 그 이상으로 늘어나면 상황은 급변합니다. 각 컨테이너의 포트 매핑, 볼륨 마운트, 환경 변수 설정, 그리고 가장 중요한 서비스 간의 네트워크 연결까지 일일이 명령어로 관리하는 것은 비효율적이고 오류를 유발하기 쉽습니다.

다음 표를 통해 수동 Docker 명령어와 Docker Compose의 주요 차이점을 비교해 보겠습니다.

특징 수동 Docker 명령어 Docker Compose
설정 관리 각 컨테이너별로 복잡한 docker run 명령어를 반복적으로 입력해야 함. 설정의 재현성 낮음. 단일 docker-compose.yml 파일로 모든 서비스의 설정을 통합 관리. 재현성 및 일관성 매우 높음.
서비스 간 연결 --link 또는 수동 네트워크 생성 후 연결해야 함. 복잡하고 오류 발생 가능성 높음. 기본적으로 동일 네트워크에 모든 서비스를 배치하여 서비스 이름으로 쉽게 통신 가능. depends_on으로 의존성 관리.
애플리케이션 시작/중지 각 컨테이너를 개별적으로 시작/중지해야 함. 순서에 대한 고려 필요. docker-compose up 한 번으로 모든 서비스를 정의된 순서대로 시작. docker-compose down으로 모든 서비스 중지 및 제거.
환경 일관성 개발자마다 다른 명령어를 사용하거나 설정 누락 가능성. docker-compose.yml 파일만 공유하면 모든 팀원이 동일한 환경에서 작업 가능.
확장성 새로운 서비스 추가 시 모든 관련 명령어를 수동으로 업데이트해야 함. docker-compose.yml 파일에 서비스 정의만 추가하면 됨. scale 명령어로 서비스 인스턴스 수 조절 가능.

Docker Compose의 핵심 장점

위 비교를 통해 알 수 있듯이, Docker Compose는 다중 서비스 애플리케이션 개발에 있어 다음과 같은 핵심적인 장점을 제공합니다.

  1. 환경 일관성 극대화: docker-compose.yml 파일 하나로 전체 개발 환경을 정의하므로, 모든 팀원이 동일한 환경에서 작업할 수 있습니다. "내 컴퓨터에서는 잘 되는데"라는 변명은 더 이상 통하지 않습니다.
  2. 쉬운 설정 및 배포: 복잡한 여러 서비스의 설정(네트워크, 볼륨, 환경 변수, 포트 등)을 하나의 YAML 파일에 명시적으로 정의합니다. 이를 통해 설정 오류를 줄이고, 새로운 환경에 배포하는 과정을 간소화합니다.
  3. 개발 생산성 향상: docker-compose up 명령 한 줄로 모든 서비스가 자동으로 시작되고 연결됩니다. 환경 설정에 드는 시간을 줄여 개발자가 핵심 로직에 더 집중할 수 있도록 돕습니다.
  4. 서비스 간 쉬운 통신: Docker Compose는 기본적으로 모든 서비스를 동일한 네트워크에 배치하며, 서비스 이름을 사용하여 서로 통신할 수 있도록 합니다. 예를 들어, 웹 서비스에서 데이터베이스에 연결할 때 IP 주소 대신 db와 같은 서비스 이름을 사용할 수 있습니다.
  5. 버전 관리 용이: docker-compose.yml 파일을 Git과 같은 버전 관리 시스템으로 관리하여, 개발 환경의 변경 이력을 추적하고 필요할 경우 이전 상태로 쉽게 되돌릴 수 있습니다.

결론적으로, Docker Compose는 로컬 개발 환경의 복잡성을 해소하고 개발 팀의 생산성을 혁신적으로 향상시키는 데 기여하는 강력한 도구입니다.

Docker Compose 파일(docker-compose.yml) 깊이 파고들기

Docker Compose의 핵심은 모든 서비스의 정의와 설정을 담고 있는 docker-compose.yml 파일입니다. 이 파일은 YAML 형식으로 작성되며, 애플리케이션의 모든 구성 요소를 선언적으로 정의합니다. 이 섹션에서는 docker-compose.yml 파일의 기본 구조와 주요 지시어들을 자세히 살펴보겠습니다.

docker-compose.yml 파일의 기본 구조

docker-compose.yml 파일은 크게 세 가지 최상위 키를 가집니다: version, services, networks, 그리고 volumes.


version: '3.8' # Docker Compose 파일 형식 버전
services:      # 애플리케이션을 구성하는 서비스들 정의
  webapp:
    # ... webapp 서비스 설정 ...
  database:
    # ... database 서비스 설정 ...
networks:      # 서비스 간 통신을 위한 네트워크 정의 (선택 사항)
  app_network:
    # ... 네트워크 설정 ...
volumes:       # 데이터를 저장하기 위한 볼륨 정의 (선택 사항)
  db_data:
    # ... 볼륨 설정 ...
  • version: Docker Compose 파일 형식의 버전을 지정합니다. 최신 기능과 문법을 사용하려면 최신 버전을 지정하는 것이 좋습니다 (예: '3.8').
  • services: 이 섹션이 가장 중요합니다. 애플리케이션을 구성하는 개별 서비스들을 정의합니다. 각 서비스는 컨테이너 하나에 해당하며, 이미지, 포트, 볼륨, 환경 변수 등 다양한 설정을 가집니다.
  • networks (선택 사항): 서비스 간 통신을 위한 사용자 정의 네트워크를 정의합니다. 명시적으로 정의하지 않으면 Docker Compose가 기본 네트워크를 생성하여 사용합니다.
  • volumes (선택 사항): 컨테이너의 데이터를 영구적으로 저장하기 위한 볼륨을 정의합니다. 컨테이너가 삭제되어도 데이터가 유지되어야 할 때 유용합니다.

주요 서비스 지시어 상세 설명

services 섹션 내에서 각 서비스는 다음과 같은 주요 지시어들을 사용하여 구성됩니다.

  • image: 컨테이너를 생성할 Docker 이미지의 이름을 지정합니다. Docker Hub와 같은 레지스트리에서 가져올 이미지를 명시할 때 사용합니다.
    
    services:
      database:
        image: postgres:14-alpine
            
  • build: Dockerfile을 사용하여 직접 이미지를 빌드해야 할 때 사용합니다. 빌드 컨텍스트(context)와 Dockerfile 경로(dockerfile)를 지정할 수 있습니다.
    
    services:
      webapp:
        build:
          context: ./webapp # Dockerfile이 위치한 경로
          dockerfile: Dockerfile.dev # 사용할 Dockerfile 이름 (기본값은 Dockerfile)
            
  • ports: 호스트 머신과 컨테이너 간의 포트 매핑을 정의합니다. "호스트포트:컨테이너포트" 형식으로 작성합니다.
    
    services:
      webapp:
        ports:
          - "8000:3000" # 호스트의 8000번 포트를 컨테이너의 3000번 포트로 연결
            
  • environment: 컨테이너 내부에 설정할 환경 변수를 정의합니다. 민감한 정보는 .env 파일로 분리하여 관리하는 것이 좋습니다.
    
    services:
      database:
        environment:
          POSTGRES_DB: mydatabase
          POSTGRES_USER: user
          POSTGRES_PASSWORD: password
            
  • volumes: 컨테이너와 호스트 머신 간에 데이터를 공유하거나, 컨테이너의 데이터를 영구적으로 저장하기 위한 볼륨을 정의합니다. "호스트경로:컨테이너경로" 또는 "볼륨이름:컨테이너경로" 형식으로 사용합니다.
    
    services:
      webapp:
        volumes:
          - ./webapp:/app # 호스트의 webapp 디렉토리를 컨테이너의 /app 디렉토리로 마운트 (코드 동기화)
      database:
        volumes:
          - db_data:/var/lib/postgresql/data # db_data라는 이름의 볼륨을 데이터 저장에 사용
            
  • networks: 해당 서비스가 연결될 네트워크를 지정합니다. 여러 네트워크에 연결하거나, 특정 네트워크에만 연결할 수 있습니다.
    
    services:
      webapp:
        networks:
          - app_network
      database:
        networks:
          - app_network
            
  • depends_on: 서비스 간의 의존성을 정의합니다. 특정 서비스가 시작된 후에 다른 서비스가 시작되도록 보장합니다. 이는 시작 순서만 제어하며, 서비스가 완전히 준비되었음을 보장하지는 않습니다.
    
    services:
      webapp:
        depends_on:
          - database
          - redis
            
  • restart: 컨테이너가 어떤 상황에서 재시작되어야 하는지 정의합니다 (예: no, on-failure, always, unless-stopped). 개발 환경에서는 noon-failure를 주로 사용합니다.
    
    services:
      webapp:
        restart: on-failure
            
  • container_name: 컨테이너에 고정된 이름을 부여합니다. 이 이름을 사용하면 컨테이너를 쉽게 식별하고 관리할 수 있습니다.
    
    services:
      database:
        container_name: myapp_db
            

이러한 지시어들을 조합하여 docker-compose.yml 파일을 작성하면, 복잡한 다중 서비스 애플리케이션로컬 개발 환경을 간결하고 명확하게 정의할 수 있습니다. 다음 섹션에서는 실제 예시를 통해 이러한 지시어들이 어떻게 활용되는지 살펴보겠습니다.

Docker Compose를 활용한 로컬 개발 환경 구축: 다중 서비스 애플리케이션 설정 가이드 - programming, html, css, javascript, php, website development, code, html code, computer code, coding, digital, computer programming, pc, www, cyberspace, programmer, web development, computer, technology, developer, computer programmer, internet, ide, lines of code, hacker, hacking, gray computer, gray technology, gray laptop, gray website, gray internet, gray digital, gray web, gray code, gray coding, gray programming, programming, programming, programming, javascript, code, code, code, coding, coding, coding, coding, coding, digital, web development, computer, computer, computer, technology, technology, technology, developer, internet, hacker, hacker, hacker, hacking

Image by Boskampi on Pixabay

다중 서비스 애플리케이션 구성 실전 가이드

이제 이론을 바탕으로 실제 다중 서비스 애플리케이션을 Docker Compose로 구성하는 실전 예제를 살펴보겠습니다. 흔히 볼 수 있는 웹 애플리케이션 스택인 "Node.js (또는 Python/Java) 백엔드 + PostgreSQL 데이터베이스 + Redis 캐시" 조합을 예시로 들어보겠습니다.

이 예제 애플리케이션은 다음과 같은 구성 요소를 가집니다:

  • backend: Node.js 기반의 API 서버 (Dockerfile로 빌드)
  • db: PostgreSQL 데이터베이스 (공식 Docker 이미지 사용)
  • redis: Redis 캐시 서버 (공식 Docker 이미지 사용)

프로젝트 구조는 다음과 같다고 가정합니다.


my-multi-service-app/
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   ├── app.js
│   └── ...
├── docker-compose.yml
└── .env

backend/Dockerfile 예시

먼저 backend 서비스에서 사용할 Dockerfile을 정의합니다. 이는 Node.js 애플리케이션을 빌드하고 실행하는 기본적인 내용입니다.


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

docker-compose.yml 예시

이제 이 모든 서비스를 연결하고 관리하는 docker-compose.yml 파일을 작성해 보겠습니다.


# docker-compose.yml
version: '3.8'

services:
  backend:
    build:
      context: ./backend # backend 디렉토리에서 Dockerfile을 찾아 빌드
      dockerfile: Dockerfile
    ports:
      - "3000:3000" # 호스트의 3000번 포트를 컨테이너의 3000번 포트로 연결
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://user:password@db:5432/mydatabase # db 서비스의 이름으로 연결
      REDIS_URL: redis://redis:6379 # redis 서비스의 이름으로 연결
    volumes:
      - ./backend:/app # 호스트의 소스 코드를 컨테이너에 마운트하여 실시간 코드 변경 반영 (핫 리로딩)
      - /app/node_modules # node_modules는 호스트에서 마운트되지 않도록 예외 처리
    depends_on:
      - db # db 서비스가 먼저 시작되도록 의존성 설정
      - redis # redis 서비스가 먼저 시작되도록 의존성 설정
    restart: on-failure # 컨테이너 실패 시 재시작
    networks:
      - app_network

  db:
    image: postgres:14-alpine # PostgreSQL 14 버전 이미지 사용
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data # 데이터 지속성을 위한 볼륨 마운트
    ports:
      - "5432:5432" # 호스트 5432 포트를 DB에 연결 (선택 사항, 내부 통신만 필요하다면 생략 가능)
    restart: unless-stopped # 컨테이너 수동 중지 전까지 재시작
    networks:
      - app_network

  redis:
    image: redis:7-alpine # Redis 7 버전 이미지 사용
    ports:
      - "6379:6379" # 호스트 6379 포트를 Redis에 연결 (선택 사항, 내부 통신만 필요하다면 생략 가능)
    restart: unless-stopped
    networks:
      - app_network

networks:
  app_network: # 모든 서비스가 공유할 사용자 정의 네트워크
    driver: bridge

volumes:
  db_data: # PostgreSQL 데이터 저장용 볼륨

주요 설정 포인트 설명

  • backend 서비스:
    • build: ./backend 디렉토리의 Dockerfile을 사용하여 이미지를 빌드합니다. 이는 로컬 소스 코드 변경 시 재빌드 과정을 거쳐 최신 코드를 반영할 수 있게 합니다.
    • ports: "3000:3000": 호스트 머신의 3000번 포트와 컨테이너의 3000번 포트를 연결합니다. 이렇게 하면 웹 브라우저에서 http://localhost:3000으로 백엔드 서비스에 접근할 수 있습니다.
    • environment: 백엔드 애플리케이션이 사용할 환경 변수를 정의합니다. 특히 DATABASE_URLREDIS_URLdbredis 서비스의 이름을 사용하여 내부 네트워크 통신이 가능하도록 설정된 것을 주목하세요. Docker Compose는 서비스 이름을 내부 DNS로 자동으로 등록해 줍니다.
    • volumes: - ./backend:/app: 이 설정은 로컬 개발 환경에서 매우 중요합니다. 호스트 머신의 ./backend 디렉토리(소스 코드)를 컨테이너의 /app 디렉토리로 마운트하여, 호스트에서 코드를 변경하면 컨테이너 내부에도 즉시 반영됩니다. 이는 핫 리로딩(Hot Reloading)을 가능하게 하여 개발 효율성을 극대화합니다. /app/node_modules를 예외 처리하여 호스트의 node_modules가 컨테이너를 덮어쓰지 않도록 합니다.
    • depends_on: dbredis 서비스가 먼저 시작된 후에 backend 서비스가 시작되도록 보장합니다.
  • db 서비스 (PostgreSQL):
    • image: postgres:14-alpine: 경량화된 PostgreSQL 14 이미지를 사용합니다.
    • environment: 데이터베이스 이름, 사용자, 비밀번호 등 필수 환경 변수를 설정합니다.
    • volumes: - db_data:/var/lib/postgresql/data: db_data라는 이름의 Docker 볼륨을 사용하여 PostgreSQL 데이터를 영구적으로 저장합니다. 컨테이너가 삭제되어도 데이터는 유지됩니다.
    • ports: "5432:5432": 호스트에서 데이터베이스 클라이언트로 직접 접근해야 할 경우에만 설정합니다. 백엔드 서비스만 데이터베이스에 접근한다면 이 포트 매핑은 생략할 수 있습니다.
  • redis 서비스:
    • image: redis:7-alpine: 경량화된 Redis 7 이미지를 사용합니다.
    • ports: "6379:6379": 호스트에서 Redis CLI 등으로 직접 접근해야 할 경우에만 설정합니다.
  • networks: app_network라는 사용자 정의 브릿지 네트워크를 생성하고 모든 서비스가 이 네트워크에 연결되도록 합니다. 이 네트워크를 통해 서비스들은 서로의 이름을 사용하여 통신할 수 있습니다.
  • volumes: db_data 볼륨을 명시적으로 정의합니다.

docker-compose.yml 파일 하나로 다중 서비스 애플리케이션의 복잡한 로컬 개발 환경을 완벽하게 설정할 수 있습니다. 이제 이 환경을 어떻게 활용하는지 다음 섹션에서 살펴보겠습니다.

개발 워크플로우 최적화: 유용한 명령어와 팁

docker-compose.yml 파일을 성공적으로 작성했다면, 이제 이를 활용하여 개발 워크플로우를 효율적으로 관리하는 방법을 익힐 차례입니다. Docker Compose가 제공하는 핵심 명령어들과 몇 가지 유용한 팁을 통해 개발 생산성을 더욱 향상시킬 수 있습니다.

주요 Docker Compose 명령어

docker-compose 명령어는 docker-compose.yml 파일이 있는 디렉토리에서 실행해야 합니다.

  • docker-compose up: 모든 서비스를 빌드하고 시작합니다. 백그라운드에서 실행하려면 -d (detached mode) 옵션을 추가합니다.
    
    docker-compose up # 포그라운드에서 모든 서비스 시작, 로그 출력
    docker-compose up -d # 백그라운드에서 모든 서비스 시작
            
    -d 옵션으로 실행하면 터미널이 자유로워지며, 서비스 로그는 docker-compose logs 명령어로 확인할 수 있습니다.
  • docker-compose down: docker-compose up으로 시작된 모든 서비스를 중지하고 컨테이너를 제거합니다. 볼륨까지 제거하려면 -v 옵션을 추가합니다.
    
    docker-compose down # 서비스 중지 및 컨테이너 제거
    docker-compose down -v # 서비스 중지, 컨테이너 및 볼륨 제거 (데이터베이스 데이터 등 초기화)
            
  • docker-compose ps: 현재 실행 중인 서비스들의 상태를 확인합니다.
    
    docker-compose ps
            
  • docker-compose logs [서비스 이름]: 특정 서비스의 로그를 확인하거나, 모든 서비스의 로그를 확인할 수 있습니다.
    
    docker-compose logs backend # backend 서비스의 로그만 확인
    docker-compose logs -f # 모든 서비스의 로그를 실시간으로 스트리밍하여 확인
            
  • docker-compose exec [서비스 이름] [명령어]: 실행 중인 특정 서비스 컨테이너 내부에서 명령어를 실행합니다. 디버깅이나 컨테이너 내부 작업에 유용합니다.
    
    docker-compose exec backend bash # backend 컨테이너 내부로 bash 셸 접속
    docker-compose exec db psql -U user mydatabase # db 컨테이너 내부에서 psql 클라이언트 실행
            
  • docker-compose build [서비스 이름]: 특정 서비스의 이미지를 다시 빌드합니다. Dockerfile을 수정했을 때 유용합니다.
    
    docker-compose build backend
            
  • docker-compose restart [서비스 이름]: 특정 서비스를 재시작합니다.
    
    docker-compose restart backend
            

개발 생산성 향상을 위한 팁

  1. 볼륨 마운트를 통한 핫 리로딩 활용:이전 섹션의 예시에서 backend 서비스에 ./backend:/app 볼륨 마운트를 설정했습니다. 이는 호스트 머신의 소스 코드 변경이 컨테이너 내부에 즉시 반영되도록 합니다. Node.js의 nodemon이나 Python의 watchgod, Java의 Spring Boot DevTools 등 파일 변경을 감지하여 애플리케이션을 자동으로 재시작하는 도구와 함께 사용하면, 코드를 수정할 때마다 컨테이너를 재빌드하거나 재시작할 필요 없이 거의 실시간으로 변경 사항을 확인할 수 있습니다.
  2. # docker-compose.yml (backend 서비스 일부) services: backend: # ... volumes: - ./backend:/app - /app/node_modules # node_modules는 컨테이너 내부의 것을 사용하도록 예외 처리 # ...
  3. .env 파일을 이용한 환경 변수 관리:민감한 정보(비밀번호, API 키 등)나 환경별로 달라지는 설정값들은 docker-compose.yml 파일에 직접 명시하는 대신 .env 파일을 사용하여 관리하는 것이 좋습니다. docker-compose.yml 파일과 같은 디렉토리에 .env 파일을 생성하면, Docker Compose가 자동으로 해당 파일의 변수들을 읽어와 environment 지시어에서 활용할 수 있습니다.
    
    # docker-compose.yml (environment 지시어에서 .env 변수 활용)
    services:
      db:
        environment:
          POSTGRES_USER: ${POSTGRES_USER}
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
            
    .env 파일은 .gitignore에 추가하여 버전 관리에서 제외하는 것이 일반적입니다.
  4. # .env 파일 예시 POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword123
  5. 테스트 환경 구성:로컬 개발 환경 외에, 별도의 docker-compose.test.yml 파일을 만들어 테스트 전용 환경을 구축할 수도 있습니다. 예를 들어, 테스트 데이터베이스를 사용하거나, 특정 테스트 스크립트를 실행하는 서비스를 추가할 수 있습니다.이렇게 -f 옵션을 사용하여 여러 Compose 파일을 병합하여 실행할 수 있습니다.
  6. docker-compose -f docker-compose.yml -f docker-compose.test.yml up --build -d

이러한 명령어와 팁들을 통해 Docker Compose를 활용한 로컬 개발 환경을 더욱 효율적으로 관리하고, 개발 워크플로우최적화할 수 있습니다.

Docker Compose를 활용한 로컬 개발 환경 구축: 다중 서비스 애플리케이션 설정 가이드 - school, old, wood, slate, teaching, blackboard, museum of local history, school, school, school, school, school, slate, teaching, teaching, teaching, teaching, blackboard

Image by congerdesign on Pixabay

트러블슈팅: 흔히 마주치는 문제와 해결책

아무리 잘 설정된 환경이라도 개발 과정에서는 예상치 못한 문제에 직면할 수 있습니다. Docker Compose 환경에서도 몇 가지 흔히 마주치는 문제들이 있으며, 이에 대한 해결책을 미리 알아두면 시간을 절약하고 효율적으로 디버깅할 수 있습니다.

1. 포트 충돌 (Port Conflict)

  • 문제: docker-compose up 실행 시 "port is already allocated" 또는 "address already in use"와 같은 오류 메시지가 발생합니다. 이는 Docker Compose가 컨테이너 포트를 호스트 포트에 매핑하려 할 때, 이미 해당 호스트 포트가 다른 애플리케이션이나 다른 컨테이너에 의해 사용 중이기 때문입니다.
  • 해결책:
    1. 사용 중인 포트 확인: netstat -tulnp | grep <포트번호> (Linux) 또는 lsof -i :<포트번호> (macOS/Linux), netstat -ano | findstr :<포트번호> (Windows) 명령어를 사용하여 어떤 프로세스가 해당 포트를 사용 중인지 확인하고 종료합니다.
    2. docker-compose.yml 수정: ports 지시어에서 호스트 포트를 다른 사용 가능한 포트 번호로 변경합니다 (예: "3001:3000").
    3. 기존 Docker 컨테이너 확인: 이전에 실행했던 Docker 컨테이너가 포트를 점유하고 있을 수 있습니다. docker ps -a로 모든 컨테이너를 확인하고, 불필요한 컨테이너는 docker rm <컨테이너ID>로 제거하거나 docker stop <컨테이너ID>로 중지합니다.

2. 서비스 간 연결 실패 (Cannot Connect to Service)

  • 문제: 백엔드 서비스가 데이터베이스나 Redis에 연결하지 못하고 "connection refused" 또는 "host not found" 오류를 발생시킵니다. depends_on을 설정했음에도 불구하고 발생할 수 있습니다.
  • 해결책:
    1. 서비스 이름 확인: 애플리케이션 코드 내에서 데이터베이스 호스트를 IP 주소 대신 docker-compose.yml에 정의된 서비스 이름(예: db, redis)으로 사용하고 있는지 확인합니다.
    2. 네트워크 확인: 모든 서비스가 동일한 네트워크에 속해 있는지 확인합니다. 명시적으로 networks를 지정했다면, 모든 관련 서비스가 해당 네트워크에 포함되어야 합니다.
    3. 서비스 준비 상태: depends_on은 단순히 시작 순서만 보장하며, 서비스가 완전히 "준비"되었음을 보장하지 않습니다. 특히 데이터베이스와 같이 시작 시간이 긴 서비스의 경우, 백엔드 애플리케이션이 연결을 시도할 때 아직 데이터베이스가 완전히 초기화되지 않았을 수 있습니다.
      • healthcheck 사용: Docker Compose 3 이상에서는 healthcheck 지시어를 사용하여 서비스의 건강 상태를 명시적으로 정의하고, 다른 서비스가 의존하기 전에 준비될 때까지 기다릴 수 있습니다.
      • 애플리케이션 레벨 재시도 로직: 애플리케이션 코드 내에 데이터베이스 연결 실패 시 일정 시간 대기 후 재시도하는 로직을 구현하는 것이 가장 견고한 방법입니다.
    4. 환경 변수 확인: 데이터베이스 사용자 이름, 비밀번호, 데이터베이스 이름 등 연결에 필요한 환경 변수가 올바르게 설정되었는지 확인합니다.

3. 볼륨 권한 문제 (Volume Permission Denied)

  • 문제: 볼륨을 마운트했을 때 컨테이너 내부에서 파일 쓰기 권한이 없거나, 호스트의 파일에 접근하지 못하는 문제가 발생합니다. "permission denied" 오류가 대표적입니다.
  • 해결책:
    1. 컨테이너 내부 사용자 확인: 컨테이너 내부에서 애플리케이션이 어떤 사용자(UID/GID)로 실행되는지 확인하고, 호스트의 마운트된 디렉토리에 해당 사용자가 접근할 수 있는 권한이 있는지 확인합니다.
    2. user 지시어 사용: docker-compose.yml의 서비스 정의에 user: "<UID>:<GID>"를 추가하여 컨테이너가 호스트의 파일 소유자와 동일한 권한으로 실행되도록 설정할 수 있습니다. id -uid -g 명령어로 호스트의 UID/GID를 확인할 수 있습니다.
    3. 권한 조정: 호스트의 해당 디렉토리 권한을 chmod -R 777 <디렉토리경로>와 같이 일시적으로 느슨하게 설정하여 테스트해 볼 수 있습니다 (운영 환경에서는 권장하지 않음).

4. 이미지 빌드 실패 (Image Build Failed)

  • 문제: docker-compose up --build 또는 docker-compose build 실행 시 서비스 이미지가 빌드되지 않고 오류가 발생합니다.
  • 해결책:
    1. Dockerfile 오류 확인: 빌드 로그를 자세히 살펴보고 Dockerfile 내부의 명령어가 올바른지, 필요한 파일이 누락되지 않았는지 확인합니다.
    2. 컨텍스트 경로 확인: build: context: ./<서비스경로>가 올바른 Dockerfile 위치를 가리키는지 확인합니다.
    3. 캐시 문제: 때로는 Docker 빌드 캐시 때문에 예상치 못한 문제가 발생할 수 있습니다. docker-compose build --no-cache 옵션을 사용하여 캐시를 사용하지 않고 새로 빌드해 봅니다.
    4. 네트워크 문제: RUN apt-get updateRUN npm install과 같은 명령어가 외부 리소스에 접근할 때 네트워크 문제가 있을 수 있습니다. 프록시 설정 등이 필요한지 확인합니다.

이러한 트러블슈팅 팁들을 통해 Docker Compose 환경에서 발생하는 일반적인 문제들을 효과적으로 진단하고 해결책을 적용하여 개발 워크플로우를 원활하게 유지할 수 있습니다.

마무리하며: 생산성 향상의 핵심 열쇠

지금까지 Docker Compose를 활용하여 다중 서비스 애플리케이션로컬 개발 환경을 효율적으로 구축하고 관리하는 방법을 상세하게 살펴보았습니다. 복잡한 환경 설정의 문제점부터 시작하여, Docker Compose의 핵심 장점, docker-compose.yml 파일의 깊이 있는 이해, 실전 구성 가이드, 그리고 개발 워크플로우 최적화 팁과 흔히 마주치는 트러블슈팅 방법까지 다루었습니다.

핵심적으로 Docker Compose는 다음 세 가지 측면에서 개발 생산성을 혁신적으로 향상시킵니다.

  1. 환경 일관성: docker-compose.yml 파일 하나로 모든 팀원이 동일하고 재현 가능한 개발 환경을 가질 수 있어, "내 컴퓨터에서는 잘 되는데"라는 문제를 근본적으로 해결합니다.
  2. 설정 간소화: 복잡한 여러 서비스의 설치 및 구성을 단일 파일에 선언적으로 정의함으로써, 설정에 드는 시간과 오류 발생 가능성을 크게 줄입니다.
  3. 개발 워크플로우 효율화: docker-compose up 명령 한 줄로 전체 애플리케이션 스택을 시작하고, 볼륨 마운트를 통한 핫 리로딩으로 코드 변경 사항을 즉시 반영하여 개발 주기를 단축합니다.

개발 환경 설정에 들이는 시간을 최소화하고, 오직 애플리케이션의 핵심 로직 개발에만 집중할 수 있게 해주는 Docker Compose는 현대 소프트웨어 개발에서 생산성 향상의 핵심 열쇠라고 할 수 있습니다. 이 가이드가 여러분의 개발 여정에 큰 도움이 되기를 바랍니다.

Docker Compose를 사용하며 겪었던 흥미로운 경험이나, 여러분만의 유용한 팁이 있다면 댓글로 공유해 주세요! 함께 더 나은 개발 문화를 만들어갈 수 있습니다.

📌 함께 읽으면 좋은 글

  • [튜토리얼] GitHub Actions CI/CD 파이프라인 구축 가이드: 웹 프로젝트 자동화 전략
  • [튜토리얼] Docker Compose로 로컬 개발 환경에서 RabbitMQ 메시지 큐 구축 및 연동 완벽 가이드
  • [이슈 분석] AI 시대 개발자 생존 전략: 새로운 역량과 미래 커리어 방향 분석

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

반응형