Docker Compose는 다중 컨테이너 도커 애플리케이션을 정의하고 실행하기 위한 도구이다. 단일 YAML 구성 파일을 사용하여 애플리케이션을 구성하는 여러 서비스(컨테이너)와 그에 필요한 네트워크, 볼륨 등의 리소스를 한 번에 설정하고 관리할 수 있다.
기본적인 Docker 명령어로는 각 컨테이너를 개별적으로 실행하고 연결해야 하는 번거로움이 있다. Docker Compose는 이러한 복잡성을 해소하며, 특히 웹 애플리케이션, 데이터베이스, 캐시 서버, 메시지 큐 등이 결합된 현대적인 애플리케이션 스택을 손쉽게 구성하고 개발, 테스트, 배포 환경을 일관되게 유지하는 데 핵심적인 역할을 한다.
주요 동작 방식은 docker-compose.yml (또는 compose.yaml) 파일을 작성한 후 docker compose up 명령어로 전체 애플리케이션 스택을 한 번에 시작하는 것이다. 반대로 docker compose down 명령어는 모든 리소스를 정리한다. 이를 통해 개발자는 복잡한 인프라 구성 명령어를 매번 입력하지 않고도, 코드와 함께 버전 관리될 수 있는 선언적 설정 파일로 전체 환경을 재현할 수 있다.
Docker Compose는 YAML 형식의 설정 파일을 사용하여 다중 컨테이너 애플리케이션을 정의하고 실행하는 도구이다. 이 파일은 일반적으로 docker-compose.yml이라는 이름을 가지며, 애플리케이션을 구성하는 모든 서비스, 네트워크, 볼륨 등의 설정을 한 곳에서 관리할 수 있게 한다. 이를 통해 복잡한 멀티 컨테이너 환경을 단일 명령어로 쉽게 구동하고 관리할 수 있다.
Docker Compose 파일의 최상위에는 주로 version, services, networks, volumes 등의 키가 정의된다. version 키는 사용할 Compose 파일 형식의 버전을 지정한다. services 섹션 아래에는 애플리케이션을 구성하는 각 컨테이너 서비스(예: 웹 서버, 데이터베이스)가 정의된다. 각 서비스는 image나 build를 통해 사용할 도커 이미지를 지정하며, ports, environment, volumes 등의 하위 키를 통해 상세 구성을 설정한다.
*서비스 정의*: services 아래에 정의된 각 항목은 하나의 실행 가능한 컨테이너를 의미한다. 예를 들어, web과 db라는 두 개의 서비스를 정의하면, 각각 독립된 컨테이너로 실행된다. 서비스는 Dockerfile을 빌드하거나 공개된 이미지를 사용할 수 있다.
*네트워크 정의*: networks 섹션을 사용하면 사용자 정의 도커 네트워크를 생성할 수 있다. 서비스는 이 네트워크에 연결되어 서로 통신할 수 있다. 명시적으로 정의하지 않으면 Compose가 기본 네트워크를 자동으로 생성한다.
*볼륨 정의*: volumes 섹션은 호스트 머신이나 도커가 관리하는 지속적 저장소를 정의한다. 이 볼륨을 서비스에 마운트하여 컨테이너의 생명주기와 무관하게 데이터를 보존한다.
간단한 docker-compose.yml 파일의 예는 다음과 같다.
```yaml
version: '3.8'
services:
web:
build: .
ports:
"5000:5000"
db:
image: postgres:13
volumes:
db_data:/var/lib/postgresql/data
volumes:
db_data:
```
Docker Compose 설정은 YAML 형식의 파일(docker-compose.yml 또는 compose.yml)을 사용하여 정의한다. 이 파일은 프로젝트의 모든 서비스, 네트워크, 볼륨 구성을 선언적으로 기술하는 역할을 한다.
파일의 최상위 구조는 주로 version, services, networks, volumes 등의 키로 구성된다. version 키는 사용할 Docker Compose 파일 형식의 버전을 지정하며, 최신 버전에서는 생략 가능하다. services 섹션은 애플리케이션을 구성하는 각 컨테이너(예: 웹 서버, 데이터베이스)를 정의하는 핵심 부분이다. 각 서비스는 image나 build로 컨테이너 이미지를 지정하고, ports, environment, volumes 등의 하위 키를 통해 상세 설정을 추가한다.
YAML 문법은 들여쓰기(공백)를 사용하여 구조를 표현하며, 주석은 # 기호로 시작한다. 리스트 항목은 하이픈(-)으로, 키-값 쌍은 콜론(:)으로 표기한다. 복잡한 문자열 값이나 여러 줄의 값을 정의할 때는 파이프(|)나 큰따옴표를 사용한다.
주요 최상위 키 | 설명 |
|---|---|
| 실행할 컨테이너 서비스들을 정의한다. 필수 항목이다. |
| 사용자 정의 네트워크를 생성하고 설정한다. |
| 명명된 볼륨이나 호스트 경로를 생성하고 관리한다. |
| 서비스에 구성 파일을 제공한다 (Swarm 모드에서 주로 사용). |
| 서비스에 비밀 데이터를 안전하게 제공한다 (Swarm 모드에서 주로 사용). |
서비스 정의 내에서 자주 사용되는 키로는 컨테이너에 사용할 이미지를 지정하는 image, 로컬 Dockerfile 경로로부터 이미지를 빌드하도록 지시하는 build, 포트 매핑을 설정하는 ports, 환경 변수를 설정하는 environment, 볼륨을 마운트하는 volumes, 그리고 다른 서비스에 대한 시작 순서 의존성을 정의하는 depends_on 등이 있다.
Docker Compose 파일의 핵심 구성 요소는 서비스, 네트워크, 볼륨이다. 이 세 가지 요소를 YAML 형식으로 정의하여 애플리케이션에 필요한 다중 컨테이너 환경을 구성한다.
서비스는 실행할 컨테이너의 구체적인 설정을 정의한다. 각 서비스는 일반적으로 하나의 이미지에 대응되며, docker-compose.yml 파일 내 services: 항목 하위에 기술된다. 주요 설정 항목으로는 사용할 이미지(image:), 컨테이너 이름(container_name:), 포트 매핑(ports:), 환경 변수(environment:), 재시작 정책(restart:), 그리고 의존성(depends_on:) 등이 있다. 서비스 정의를 통해 각 컨테이너의 동작 방식을 세밀하게 제어할 수 있다.
네트워크는 서비스 간 통신을 위한 격리된 네트워크 환경을 생성한다. networks: 항목을 사용하여 사용자 정의 네트워크를 정의할 수 있으며, 서비스 정의 내부에서 해당 네트워크에 연결(networks: - 네트워크명)한다. 기본적으로 Compose는 프로젝트 전용의 단일 네트워크를 자동 생성하여 모든 서비스를 연결하지만, 복잡한 아키텍처에서는 다중 네트워크를 정의하여 서비스 그룹을 논리적으로 분리할 수 있다.
볼륨은 컨테이너의 데이터를 영속적으로 저장하거나 호스트와 데이터를 공유하기 위한 메커니즘이다. volumes: 항목에서 볼륨을 이름으로 정의하고, 서비스 설정 내 volumes: 항목에서 특정 경로에 마운트한다. 볼륨은 호스트 파일 시스템의 특정 디렉토리(/path/on/host:/path/in/container)에 직접 매핑하거나, Docker가 관리하는 명명된 볼륨(volume_name:/path/in/container)을 사용할 수 있다. 이를 통해 데이터베이스의 데이터나 애플리케이션 로그 등을 컨테이너 생명주기와 독립적으로 보존한다.
구성 요소 | 키워드 | 주요 목적 | 예시 (YAML 코드 조각) |
|---|---|---|---|
서비스 |
| 컨테이너 실행 정의 |
|
네트워크 |
| 컨테이너 통신 네트워크 정의 |
|
볼륨 |
| 데이터 영속성 및 공유 |
|
멀티 컨테이너 설정의 핵심은 여러 개의 독립적인 컨테이너를 하나의 애플리케이션 유닛으로 조율하는 것이다. 이를 위해 Docker Compose는 서비스 간 의존성, 네트워크 격리 및 연결, 데이터 지속성을 위한 공유 스토리지와 같은 구성 요소를 제공한다. 이러한 요소들을 정의함으로써 복잡한 애플리케이션 스택을 단일 명령어로 구성하고 관리할 수 있다.
서비스 간의 시작 순서와 의존성은 depends_on 지시어로 제어한다. 이 설정은 특정 서비스가 다른 서비스에 의존함을 선언하여, 의존된 서비스의 컨테이너가 먼저 시작되도록 보장한다. 예를 들어, 웹 애플리케이션 서비스는 데이터베이스 서비스에 depends_on을 설정할 수 있다. 그러나 이는 컨테이너의 실행 순서만을 보장할 뿐, 해당 서비스(예: MySQL)가 실제로 요청을 받을 준비가 될 때까지의 대기 상태는 관리하지 않는다. 완전한 준비 상태를 보장하려면 스크립트나 헬스체크와 같은 추가적인 메커니즘이 필요하다.
네트워크 구성은 컨테이너 간 통신의 기반이다. Docker Compose 파일에 네트워크를 명시적으로 정의하지 않으면, 모든 서비스는 기본적으로 생성되는 단일 네트워크에 연결되어 서비스 이름을 호스트명으로 사용하여 서로 통신할 수 있다. 사용자 정의 네트워크를 생성하면 서비스 그룹을 논리적으로 분리하고, IP 주소 대역이나 DNS 설정과 같은 세부 구성을 제어할 수 있다. 이는 보안을 강화하거나 특정 서비스들만 내부 통신하도록 격리하는 데 유용하다.
데이터의 지속성과 공유는 볼륨과 바인드 마운트를 통해 구현된다. 볼륨은 Docker가 관리하는 지속적 스토리지 메커니즘으로, 여러 컨테이너에서 안전하게 공유될 수 있다. 예를 들어, 애플리케이션 로그를 수집하는 컨테이너와 웹 서버 컨테이너가 동일한 로그 디렉토리 볼륨을 마운트할 수 있다. 바인드 마운트는 호스트 머신의 특정 파일 시스템 경로를 컨테이너에 직접 연결한다. 개발 환경에서는 소스 코드 디렉토리를 마운트하여 호스트에서의 코드 변경이 컨테이너 내에서 즉시 반영되도록 구성하는 것이 일반적이다.
구성 요소 | 주요 키워드 | 설명 |
|---|---|---|
서비스 의존성 |
| 서비스의 시작 순서를 정의한다. 컨테이너 A가 컨테이너 B에 의존하면 B가 먼저 실행된다. |
네트워크 |
| 사용자 정의 네트워크를 생성하고 서비스를 특정 네트워크에 연결하여 통신 경로를 제어한다. |
볼륨 |
| 컨테이너 간, 또는 컨테이너와 호스트 간에 데이터를 공유하고 지속시키기 위한 스토리지를 정의한다. |
depends_on 지시어는 Docker Compose 파일 내에서 서비스 간의 시작 및 종료 순서를 제어합니다. 이 설정은 특정 서비스가 다른 서비스에 기능적으로 의존할 때, 예를 들어 애플리케이션 서버가 데이터베이스가 준비된 후에만 시작되어야 할 때 유용합니다.
depends_on은 두 가지 주요 조건을 정의할 수 있습니다. 첫 번째는 단순한 시작 순서 제어입니다. depends_on에 나열된 서비스는 해당 서비스보다 먼저 시작됩니다. 그러나 이는 단순히 컨테이너 프로세스가 실행되기 시작하는 시점만을 보장할 뿐, 서비스 내 애플리케이션이 실제로 요청을 받을 준비가 되었는지는 확인하지 않습니다. 두 번째는 상태 확인을 통한 준비 완료 대기입니다. condition 서브 옵션을 사용하여 service_healthy(헬스 체크가 성공할 때까지 대기), service_started(기본값, 컨테이너가 시작되기만을 대기), service_completed_successfully(일회성 컨테이너가 성공적으로 종료될 때까지 대기) 조건을 지정할 수 있습니다.
다음은 depends_on을 사용한 YAML 설정 예시입니다.
서비스 | 의존 대상 | 조건 | 설명 |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
| 데이터베이스 마이그레이션을 수행하는 일회성 작업으로, |
depends_on은 컨테이너 간의 시작 종속성을 명시적으로 정의함으로써 시스템의 시작 신뢰성을 높입니다. 하지만 이는 Docker Compose 수준의 의존성일 뿐, 애플리케이션 내부에서 연결 장애를 처리하는 로직(예: 재시도 메커니즘)을 대체하지는 않습니다.
Docker Compose는 기본적으로 프로젝트 전용의 브리지 네트워크를 자동으로 생성합니다. 이 네트워크 내의 모든 컨테이너는 서비스 이름을 호스트명으로 사용하여 서로 통신할 수 있습니다. 예를 들어, web 서비스와 db 서비스가 정의되어 있다면, web 컨테이너는 db라는 호스트명으로 MySQL 데이터베이스에 접근할 수 있습니다.
사용자는 docker-compose.yml 파일의 최상위 networks 키를 통해 사용자 정의 네트워크를 생성하고 구성할 수 있습니다. 각 네트워크는 특정 드라이버(예: bridge, overlay)를 사용하도록 설정하거나, 외부에 이미 존재하는 네트워크를 연결할 수 있습니다. 서비스는 networks 키 아래에 나열된 네트워크 이름을 지정하여 하나 이상의 네트워크에 연결됩니다.
네트워크 구성 요소 | 설명 | 예시 |
|---|---|---|
기본 네트워크 |
|
|
사용자 정의 네트워크 |
|
|
네트워크 앨리어스 | 컨테이너 내에서 사용할 추가 호스트명을 지정. |
|
외부 네트워크 | 이미 존재하는 네트워크를 프로젝트에 연결. |
|
복잡한 애플리케이션의 경우, 서로 다른 서비스 그룹을 논리적으로 분리하기 위해 여러 네트워크를 구성하는 것이 일반적입니다. 예를 들어, 웹 서버와 애플리케이션 서버는 frontend 네트워크에, 애플리케이션 서버와 데이터베이스는 backend 네트워크에 배치하여 보안과 트래픽 관리를 강화할 수 있습니다. 컨테이너 간 통신은 IP 주소가 아닌 서비스 이름을 통해 이루어지므로, 컨테이너가 재시작되어 IP가 변경되더라도 애플리케이션 구성에 영향을 주지 않습니다.
볼륨은 Docker 컨테이너의 데이터를 지속적으로 저장하고 컨테이너 간에 공유하기 위한 메커니즘이다. Docker Compose를 사용하면 docker-compose.yml 파일에서 볼륨을 정의하고, 여러 서비스(컨테이너)에 마운트하여 데이터를 공유하거나 영구적으로 보존할 수 있다. 이는 데이터베이스의 데이터 디렉터리, 애플리케이션 로그, 정적 파일 또는 구성 파일을 관리할 때 특히 유용하다.
공유 볼륨 설정은 주로 volumes 최상위 키와 서비스 정의 내의 volumes 키를 조합하여 구성한다. 최상위에서 명명된 볼륨을 생성하면, Docker가 해당 볼륨의 수명 주기를 관리한다. 서비스에서는 이 볼륨을 컨테이너 내부의 특정 경로에 마운트한다. 여러 서비스가 동일한 볼륨을 마운트하면 데이터를 실시간으로 공유할 수 있다. 또한 호스트 머신의 특정 디렉터리를 직접 마운트하는 바인드 마운트 방식도 지원하여, 개발 중 코드 변경을 즉시 컨테이너에 반영하는 데 자주 사용된다.
다음은 웹 애플리케이션과 데이터베이스가 공유 볼륨을 사용하는 간단한 예시이다.
```yaml
services:
web:
image: nginx:alpine
volumes:
app-data:/usr/share/nginx/html
./config/nginx.conf:/etc/nginx/nginx.conf:ro
db:
image: mysql:8.0
volumes:
db-data:/var/lib/mysql
volumes:
app-data:
db-data:
```
이 예시에서 app-data와 db-data는 Docker가 관리하는 명명된 볼륨이다. web 서비스는 정적 파일을 저장하기 위해 app-data 볼륨을 사용하고, 동시에 호스트의 ./config/nginx.conf 파일을 읽기 전용(ro)으로 마운트한다. db 서비스는 데이터를 영구 저장하기 위해 db-data 볼륨을 사용한다. 이를 통해 컨테이너를 삭제하고 재생성해도 데이터는 볼륨에 안전하게 보존된다.
환경 변수는 Docker Compose 애플리케이션의 설정과 동작을 제어하는 핵심 요소이다. 컨테이너화된 서비스의 구성(예: 데이터베이스 연결 문자열, API 키, 실행 모드)을 외부에서 주입하고, 개발, 테스트, 운영 환경에 따라 다른 값을 쉽게 적용할 수 있게 한다. 이를 통해 설정 정보를 코드나 이미지에서 분리하여 보안성을 높이고, 환경 간 이식성을 확보한다.
주로 .env 파일을 프로젝트 루트 디렉터리에 생성하여 관리한다. 이 파일은 KEY=VALUE 형식으로 작성되며, Docker Compose는 docker-compose up 명령 실행 시 자동으로 이 파일을 읽어 변수를 로드한다. docker-compose.yml 파일 내에서는 ${VARIABLE_NAME} 또는 $VARIABLE_NAME 구문을 사용하여 이 변수들을 참조할 수 있다. 중요한 비밀 정보는 .env 파일을 .gitignore에 추가하여 버전 관리 시스템에 커밋되지 않도록 해야 한다[1].
서비스별로 환경 변수를 설정하는 방법은 다양하다. docker-compose.yml 파일의 서비스 정의 내 environment: 지시어를 사용하면 직접 키-값 쌍을 나열할 수 있다. 또는 env_file: 지시어를 통해 특정 서비스만 별도의 환경 변수 파일을 지정하여 로드할 수 있다. 이는 전체 애플리케이션 공통 변수는 .env 파일에, 특정 서비스(예: 데이터베이스)에만 필요한 비밀번호는 별도 파일로 관리할 때 유용하다.
설정 방법 | 예시 | 특징 |
|---|---|---|
|
| Compose 파일 전체에서 참조 가능 |
|
| 서비스 정의 내에 직접 하드코딩 |
|
| 서비스별로 독립된 변수 파일 관리 |
환경 변수 값이 설정되지 않았을 경우를 대비해 docker-compose.yml에서 기본값을 제공할 수도 있다(예: ${VARIABLE:-default}). 또한, 호스트 머신의 기존 환경 변수를 컨테이너에 전달하려면 environment: 지시어에서 값 없이 변수명만 나열하면 된다(예: - HOST_UID).
.env 파일은 Docker Compose에서 환경 변수를 관리하기 위한 텍스트 파일이다. 이 파일은 key=value 형식으로 구성되며, docker-compose.yml 파일 내에서 ${변수명} 구문을 통해 참조된다.
.env 파일을 활용하면 설정값을 코드와 분리하여 관리할 수 있다. 예를 들어, 데이터베이스 비밀번호나 API 키와 같은 민감한 정보를 소스 코드 저장소에 직접 포함시키지 않고 별도로 관리할 수 있다. 또한, 개발, 테스트, 운영 환경에 따라 다른 설정값을 쉽게 적용할 수 있다. Docker Compose는 기본적으로 프로젝트 디렉토리에 위치한 .env 파일을 자동으로 로드한다. 파일명을 다르게 지정하려면 --env-file 옵션을 사용할 수 있다[2].
환경 변수 예시 | 설명 |
|---|---|
| 데이터베이스 접속 비밀번호 |
| 애플리케이션이 사용할 포트 번호 |
| 애플리케이션 실행 환경 |
docker-compose.yml 파일에서는 다음과 같이 참조한다.
```yaml
services:
app:
image: myapp
environment:
DATABASE_PASSWORD=${DB_PASSWORD}
PORT=${APP_PORT}
```
.env 파일에 정의되지 않은 변수를 참조할 경우, 쉘의 환경 변수 값을 사용한다. 두 곳 모두에 정의되지 않으면 빈 문자열로 처리된다. 보안을 위해 .env 파일은 .gitignore 파일에 추가하여 버전 관리 시스템에 커밋되지 않도록 해야 한다.
서비스별 환경 변수 설정은 docker-compose.yml 파일 내 각 서비스 정의 블록에서 environment 키를 사용하여 수행한다. 이 키 아래에는 키-값 쌍의 목록을 직접 나열하거나, 값을 변수로 참조하는 방식으로 설정을 정의할 수 있다.
environment 설정은 크게 두 가지 형식으로 작성한다. 첫 번째는 인라인 방식으로, 변수명: 값 형태로 직접 명시한다. 두 번째는 환경 변수 파일(.env 파일)이나 호스트 시스템의 환경 변수에서 값을 가져오는 방식이다. 후자의 경우 변수명만 작성하거나, ${변수명} 또는 $변수명 구문을 사용하여 참조한다. 값에 $ 문자가 포함될 경우 $$로 이스케이프 처리해야 한다.
다음은 데이터베이스 서비스와 웹 애플리케이션 서비스에 환경 변수를 설정하는 예시이다.
```yaml
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secretpassword
MYSQL_DATABASE: myapp_db
MYSQL_USER: user
MYSQL_PASSWORD: userpassword
webapp:
image: my-webapp:latest
environment:
DATABASE_HOST=db
DATABASE_PORT=3306
NODE_ENV=production
API_KEY=${EXTERNAL_API_KEY}
```
서비스별 설정은 해당 컨테이너 내부에서만 유효하며, 다른 서비스와 공유되지 않는다. 만약 여러 서비스가 동일한 변수를 필요로 한다면, .env 파일에 정의하고 각 서비스의 environment 블록에서 참조하는 방식으로 중복을 피할 수 있다. 또한, env_file 지시자를 사용하여 서비스별로 별도의 환경 변수 파일을 지정할 수도 있다. 이는 비밀번호나 API 키와 같은 민감한 구성 정보를 코드 저장소에서 분리하는 데 유용하다.
실전 설정 예시에서는 Docker Compose를 사용하여 실제 애플리케이션 스택을 구성하는 방법을 보여준다. 가장 일반적인 패턴은 웹 애플리케이션 스택이며, 여기서는 Nginx 웹 서버, Node.js 애플리케이션 서버, MySQL 데이터베이스로 구성된 3계층 아키텍처를 예로 든다. 각 서비스는 독립적인 컨테이너로 실행되며, Docker Compose 파일을 통해 서비스 간 의존성, 네트워크, 데이터 영속성을 정의한다.
다음은 해당 스택을 위한 docker-compose.yml 파일의 예시이다.
```yaml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
"80:80"
volumes:
./nginx.conf:/etc/nginx/nginx.conf
./static:/usr/share/nginx/html
depends_on:
app
networks:
app-network
app:
build: ./node-app
environment:
NODE_ENV=production
DB_HOST=mysql
DB_USER=root
DB_PASSWORD=${DB_PASSWORD}
volumes:
./node-app:/usr/src/app
/usr/src/app/node_modules
depends_on:
mysql:
condition: service_healthy
networks:
app-network
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
MYSQL_DATABASE=myapp
volumes:
mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
networks:
app-network
volumes:
mysql-data:
networks:
app-network:
driver: bridge
```
이 설정에서 app 서비스는 mysql 서비스가 건강 검사를 통과한 후에만 시작되도록 depends_on 조건을 설정한다. nginx 서비스는 사용자 정의 설정 파일과 정적 파일을 호스트로부터 마운트하며, app 서비스에 의존한다. 모든 서비스는 사용자 정의 브리지 네트워크인 app-network에 연결되어 서비스명으로 서로 통신할 수 있다. MySQL 데이터는 mysql-data라는 Docker 볼륨에 저장되어 컨테이너 재생성 시에도 유지된다.
또 다른 일반적인 예시는 마이크로서비스 아키텍처 구성이다. 예를 들어, API 게이트웨이, 사용자 서비스, 주문 서비스, 그리고 공통 메시지 브로커로 구성된 시스템을 생각해 볼 수 있다. 이 경우 각 마이크로서비스는 별도의 디렉터리에 코드와 Dockerfile을 가지며, Docker Compose 파일은 이들을 하나의 애플리케이션으로 통합한다. 서비스 간 통신은 내부 네트워크를 통해 이루어지며, Redis나 RabbitMQ와 같은 공유 서비스는 다른 모든 서비스에 의해 의존될 수 있다. 이러한 설정의 핵심은 명확하게 정의된 네트워크와 서비스 발견(도메인 이름)을 통해 각 서비스가 독립적으로 확장되고 관리될 수 있도록 하는 것이다.
이 예시는 Nginx 웹 서버, Node.js 애플리케이션 서버, MySQL 데이터베이스로 구성된 전형적인 3계층 웹 애플리케이션의 Docker Compose 설정을 보여준다. 각 서비스는 독립적인 컨테이너로 실행되며, Compose 파일을 통해 네트워크와 의존성을 정의하여 하나의 통합된 애플리케이션으로 동작하도록 구성한다.
docker-compose.yml 파일의 핵심은 세 개의 서비스(web, app, db)와 하나의 사용자 정의 네트워크, 그리고 볼륨 정의이다. db 서비스는 공식 MySQL 이미지를 사용하며, MYSQL_ROOT_PASSWORD 등의 환경 변수와 데이터 영속성을 위한 볼륨(db_data)을 설정한다. app 서비스는 Dockerfile로부터 빌드된 Node.js 애플리케이션을 실행하며, depends_on 지시어를 통해 db 서비스가 먼저 시작되도록 보장한다. web 서비스는 Nginx 이미지를 사용하며, 호스트의 ./nginx.conf 파일을 컨테이너 내 설정 파일로 마운트하여 app 서비스로의 프록시 패스를 구성한다. 모든 서비스는 app-network라는 동일한 사용자 정의 네트워크에 연결되어 서로 통신할 수 있다.
서비스명 | 이미지/빌드 | 포트 매핑 | 의존성 | 주요 설정 |
|---|---|---|---|---|
|
|
|
| 사용자 정의 nginx.conf 마운트 |
|
|
|
| 환경 변수 파일(.env) 로드 |
|
|
| 없음 | 볼륨( |
이 설정을 실행하려면 애플리케이션 코드와 Dockerfile이 위치한 ./app 디렉터리, Nginx 설정 파일이 준비된 상태에서 docker-compose up -d 명령을 실행한다. 이 명령은 세 컨테이너를 모두 시작하며, Nginx는 사용자 요청을 받아 Node.js 앱에 전달하고, 앱은 필요한 데이터를 MySQL 데이터베이스에서 조회하는 구조가 완성된다. 데이터베이스의 데이터는 db_data 볼륨에 저장되어 컨테이너 재시작 후에도 유지된다.
마이크로서비스 아키텍처는 단일 애플리케이션을 여러 개의 독립적이고 느슨하게 결합된 서비스의 집합으로 구성하는 소프트웨어 개발 기법이다. Docker Compose는 이러한 각 서비스를 개별 컨테이너로 정의하고, 서비스 간의 통신, 의존성, 공유 자원을 하나의 docker-compose.yml 파일에서 선언적으로 관리할 수 있는 환경을 제공한다. 이를 통해 개발자는 복잡한 분산 시스템을 로컬 환경에서 쉽게 구성하고 실행할 수 있다.
마이크로서비스 구성에서는 일반적으로 API 게이트웨이, 사용자 서비스, 주문 서비스, 상품 서비스, 메시지 큐, 데이터베이스 등 각 기능 모듈이 별도의 서비스로 정의된다. docker-compose.yml 파일에서는 각 모듈을 services 하위에 명시하고, depends_on 지시어를 사용해 서비스 간 시작 순서를 제어한다. 예를 들어, 애플리케이션 서비스는 MySQL이나 Redis 컨테이너가 먼저 실행된 후에 시작되도록 설정할 수 있다. 서비스 간 통신은 Compose가 자동으로 생성하는 기본 브리지 네트워크를 통해 이루어지며, 컨테이너 이름을 호스트명으로 사용하여 서로를 발견하고 통신한다.
서비스명 | 이미지 | 포트 | 의존성 | 설명 |
|---|---|---|---|---|
|
|
|
| 외부 요청을 받아 내부 서비스로 라우팅하는 API 게이트웨이 |
|
|
|
| 사용자 관리 기능을 담당하는 마이크로서비스 |
|
|
|
| 주문 처리 기능을 담당하는 마이크로서비스 |
|
|
| - | 사용자 서비스 전용 데이터베이스 |
|
|
| - | 서비스 간 비동기 통신을 위한 메시지 브로커 |
이러한 구성의 장점은 각 서비스를 독립적으로 개발, 배포, 확장할 수 있다는 점이다. Docker Compose를 사용하면 전체 시스템을 docker-compose up 명령 하나로 시작하거나, 특정 서비스만 선택적으로 실행할 수 있다. 또한, 다중 Compose 파일을 활용하여 개발 환경용, 테스트 환경용, 프로덕션 환경용 설정을 분리하고 조합하여 사용할 수 있다. 이를 통해 마이크로서비스 아키텍처의 복잡성을 관리하면서도 일관된 로컬 개발 및 테스트 경험을 제공한다.
Docker Compose의 고급 설정 기법은 복잡한 애플리케이션 스택을 더 효율적이고 유연하게 구성하는 데 도움을 준다.
확장 구성(extends)을 사용하면 공통 설정을 재사용할 수 있다. 기본이 되는 서비스 구성을 하나의 파일에 정의한 후, 다른 Compose 파일에서 이를 상속받아 추가 설정이나 오버라이드할 수 있다. 이는 여러 환경에서 공통적인 서비스 설정을 중앙에서 관리할 때 유용하다. 다중 Compose 파일 관리는 -f 플래그를 사용하여 여러 YAML 파일을 동시에 지정하는 방식으로 작동한다. 일반적으로 docker-compose.yml을 기본으로 두고, docker-compose.override.yml을 통해 개발 환경별 설정을 추가하거나, docker-compose.prod.yml과 같은 파일을 생성하여 환경별로 다른 구성을 적용한다. 명령어 실행 시 여러 파일을 나열하면, 뒤에 오는 파일의 설정이 앞선 파일의 설정을 병합하거나 덮어쓴다.
프로파일(profiles) 기능은 단일 compose.yml 파일 내에서 특정 서비스 그룹을 선택적으로 실행할 수 있게 한다. 서비스 정의에 profiles: 키를 추가하여 프로파일 이름을 지정하면, docker compose --profile 프로파일명 up 명령으로 해당 프로파일에 속한 서비스만 시작한다. 이는 개발 중에 디버깅용 도구(예: Redis Commander)나 분석 서비스만 따로 띄우거나, 프로덕션 환경에서만 필요한 보조 서비스를 구분할 때 효과적이다.
기법 | 주요 키워드/플래그 | 주요 활용 목적 |
|---|---|---|
확장 구성 |
| 공통 서비스 설정의 재사용과 중앙 관리 |
다중 파일 관리 |
| 환경별(개발/테스트/운영) 설정 분리 |
프로파일 |
| 특정 목적의 서비스 그룹을 선택적 실행 |
이러한 기법들을 조합하면, 하나의 프로젝트 코드베이스에서도 상황과 목적에 맞게 매우 다양한 컨테이너 구성으로 유연하게 전환할 수 있다.
Docker Compose의 extends 키워드는 공통 설정을 재사용하여 YAML 파일의 중복을 줄이고 관리를 단순화하는 기능을 제공한다. 이 기능을 사용하면 하나의 기본 서비스 구성을 정의하고, 다른 서비스에서 해당 구성을 상속받아 확장하거나 재정의할 수 있다. 주로 여러 서비스가 공유하는 공통 환경 변수, 네트워크, 볼륨, 레이블 또는 기본 명령어 등을 별도의 파일로 분리할 때 유용하다.
extends를 사용하려면 상속받을 서비스의 정의가 동일한 파일 내에 있거나, 다른 Compose 파일에 존재해야 한다. 상속은 file과 service 두 가지 키를 지정하여 이루어진다. file은 기본 설정이 포함된 Compose 파일의 경로를, service는 상속받을 서비스의 이름을 가리킨다. 하위 서비스에서는 상속받은 설정에 새로운 항목을 추가하거나, 기존 항목(예: environment, ports, command)을 완전히 재정의할 수 있다. 그러나 links나 volumes_from과 같은 특정 레거시 옵션은 상속이 제한될 수 있다.
다음은 extends를 활용한 간단한 예시이다. 공통 웹 앱 설정을 common-services.yml에 정의하고, 개발 환경용 설정에서 이를 확장한다.
common-services.yml:
```yaml
services:
app-base:
image: my-app:base
environment:
NODE_ENV=production
LOG_LEVEL=info
volumes:
./app:/code
```
docker-compose.yml:
```yaml
services:
webapp:
extends:
file: common-services.yml
service: app-base
ports:
"8080:80"
environment:
NODE_ENV=development # production에서 재정의
command: npm run dev # 기본 command를 재정의
```
이 방식은 여러 환경(개발, 테스트, 스테이징)에서 동일한 기본 이미지를 사용하되, 포트, 환경 변수, 명령어만 다르게 구성해야 할 때 특히 효율적이다. 그러나 extends 기능은 Compose 파일 버전 2.1 이상에서 공식 지원되며, 최신 Compose 스펙(버전 3.x 이상)에서는 하위 호환성을 위해 존재하지만, 공식 문서에서는 중복 구성을 최소화하는 방법으로 다중 Compose 파일(-f 옵션)을 조합하는 방식을 더 권장하는 경향이 있다[3].
하나의 docker-compose.yml 파일로 모든 환경과 구성을 관리하는 것은 복잡성을 증가시킬 수 있다. 따라서 Docker Compose는 여러 Compose 파일을 조합하여 사용하는 방식을 지원한다. -f 플래그를 사용하여 여러 파일을 지정하거나, 기본적으로 docker-compose.yml과 docker-compose.override.yml 파일을 자동으로 병합하여 적용한다.
주요 사용 패턴은 다음과 같다.
사용 목적 | 파일명 예시 | 설명 |
|---|---|---|
기본 구성 |
| 모든 환경에 공통으로 적용되는 필수 서비스와 설정을 정의한다. |
환경별 재정의 |
| 개발 환경에서 자동으로 로드되며, 포트 매핑, 볼륨 마운트, 디버깅 설정 등을 추가한다. |
프로덕션 구성 |
| 기본 구성에 프로덕션 전용 설정(로그 드라이버, 배포 설정, 비밀 관리)을 덮어쓰거나 추가한다. |
기능별 확장 |
| 리버스 프록시나 모니터링 스택과 같은 특정 기능을 위한 서비스만 별도로 정의한다. |
파일은 지정된 순서대로 병합된다. 나중에 지정된 파일의 설정이 이전 파일의 설정을 덮어쓴다. 예를 들어, docker-compose -f docker-compose.yml -f docker-compose.prod.yml up 명령어를 실행하면, prod.yml 파일의 설정이 기본 yml 파일의 동일 항목을 재정의한다. 이를 통해 코드 재사용성을 높이고, 환경별 차이점을 명확하게 분리하여 관리할 수 있다.
Docker Compose 프로파일은 단일 docker-compose.yml 파일 내에서 서비스 집합을 선택적으로 활성화하여 개발, 테스트, 스테이징, 프로덕션 등 다양한 환경에 맞는 구성을 정의할 수 있게 해주는 기능이다. 프로파일을 사용하면 환경별로 필요한 서비스만 실행하거나, 동일한 서비스에 대해 환경별로 다른 설정을 적용할 수 있다. 이는 설정 파일의 중복을 줄이고 관리 효율성을 높이는 데 기여한다.
서비스에 프로파일을 할당하려면 docker-compose.yml 파일의 서비스 정의에 profiles 키를 추가한다. 프로파일을 지정하지 않은 서비스는 항상 실행되지만, 하나 이상의 프로파일이 명시된 서비스는 --profile 플래그로 해당 프로파일을 활성화했을 때만 실행된다. 예를 들어, 개발 시에만 필요한 디버깅 도구나 모니터링 에이전트를 dev 프로파일에, 테스트 시에만 필요한 데이터베이스 시딩 스크립트를 test 프로파일에 포함시킬 수 있다.
프로파일 이름 | 주요 용도 | 예시 서비스 |
|---|---|---|
| 로컬 개발 환경 | phpMyAdmin, 디버거, 파일 변경 감지(watcher) |
| 자동화 테스트 실행 | 테스트 전용 데이터베이스, 테스트 러너 |
| 모니터링 및 로깅 | Prometheus, Grafana, 로그 수집기 |
| 문제 진단 | 쉘 접근 컨테이너, 네트워크 분석 도구 |
프로파일은 docker-compose --profile <프로파일명> up 명령어로 활성화하여 사용한다. 여러 프로파일을 동시에 지정하거나, COMPOSE_PROFILES 환경 변수를 설정하여 기본 프로필을 정의할 수도 있다. 또한, docker-compose config 명령을 사용하면 활성화된 프로파일에 따른 최종 서비스 구성을 확인하여 설정 오류를 사전에 점검할 수 있다. 프로파일 기능은 특히 마이크로서비스 아키텍처에서 특정 기능 모듈만을 조건부로 실행해야 하는 복잡한 시나리오에서 유용하게 활용된다.
컨테이너 상태를 확인하려면 docker-compose ps 명령을 사용한다. 이 명령은 현재 프로젝트 디렉토리에서 docker-compose.yml 파일에 정의된 모든 서비스의 실행 상태, 포트 매핑, 컨테이너 이름을 보여준다. 보다 상세한 정보를 얻기 위해서는 docker-compose top 명령으로 각 서비스 내부에서 실행 중인 프로세스 목록을 확인하거나, docker-compose events 명령으로 실시간 컨테이너 이벤트 스트림을 모니터링할 수 있다. 컨테이너의 자원 사용량(CPU, 메모리, 네트워크 I/O)은 docker stats 명령에 컨테이너 이름이나 ID를 지정하여 모니터링한다.
로그 관리는 docker-compose logs 명령을 기본으로 한다. 특정 서비스의 로그만 보려면 docker-compose logs [서비스명]을, 실시간 로그 스트림을 확인하려면 -f 옵션을 추가한다. 여러 서비스의 로그를 통합적으로 수집하고 분석하기 위해 ELK 스택(Elasticsearch, Logstash, Kibana)이나 Fluentd와 같은 로그 집계 도구를 별도 컨테이너로 구성하여 연동하는 것이 일반적이다. 이를 통해 모든 애플리케이션 로그를 중앙에서 검색하고 시각화할 수 있다.
명령어 | 설명 | 주요 옵션 |
|---|---|---|
| 정의된 모든 서비스의 상태 목록을 표시한다. | - |
| 각 서비스 컨테이너 내에서 실행 중인 프로세스를 보여준다. | - |
| 서비스 컨테이너의 로그 출력을 보여준다. |
|
| 실시간 컨테이너 자원 사용량 통계를 표시한다. | 컨테이너 이름 또는 ID 지정 |
운영 환경에서는 프로메테우스와 그라파나를 연동하여 컨테이너 메트릭을 수집하고 대시보드를 구성하는 것이 일반적이다. 또한, docker-compose up -d 명령으로 데몬 모드로 실행한 애플리케이션을 중지할 때는 docker-compose down 명령을 사용한다. 이 명령은 컨테이너를 중지하고 제거하며, --volumes 옵션을 추가하면 정의된 볼륨까지 함께 삭제한다[4].
컨테이너 상태 모니터링은 Docker Compose로 실행 중인 애플리케이션의 건강 상태를 확인하고 문제를 조기에 발견하는 핵심 운영 활동이다. docker-compose ps 명령어는 모든 서비스의 컨테이너 상태, 포트 매핑, 이름을 한눈에 보여주는 기본 도구이다. docker-compose top 명령어를 사용하면 각 컨테이너 내부에서 실행 중인 프로세스 목록을 확인할 수 있다. 더 자세한 실시간 상태 정보는 docker-compose logs --follow [서비스명] 명령어로 특정 서비스의 로그 출력을 스트리밍하여 확인한다. 로그는 컨테이너의 표준 출력(stdout)과 표준 에러(stderr)를 캡처하므로, 애플리케이션의 동작과 오류 메시지를 추적하는 데 필수적이다.
컨테이너의 자원 사용량과 성능 지표를 모니터링하려면 docker stats 명령어를 사용한다. 이 명령어는 실행 중인 모든 컨테이너의 실시간 CPU 사용률, 메모리 사용량, 네트워크 입출력, 블록 입출력 등의 메트릭을 제공한다. 특정 Docker Compose 프로젝트의 컨테이너만 필터링하여 보려면 docker stats $(docker-compose ps -q)와 같은 명령어 조합을 활용할 수 있다. 이러한 기본 도구 외에도, cAdvisor, Prometheus, Grafana와 같은 전문 모니터링 도구를 연동하여 시계열 데이터를 수집하고 대시보드를 구성하는 것이 일반적이다. 이러한 도구들은 컨테이너 단위의 세분화된 메트릭과 역사적 데이터 분석을 가능하게 한다.
서비스의 건강 상태를 사전에 정의하고 주기적으로 검사하는 헬스 체크를 docker-compose.yml 파일에 구성할 수 있다. 다음은 헬스 체크 설정의 예시이다.
지시자 | 설명 | 예시 |
|---|---|---|
| 건강 상태를 판단하는 명령어 |
|
| 검사 실행 간격 |
|
| 검사 타임아웃 시간 |
|
| 실패 후 재시도 횟수 |
|
| 컨테이너 초기화 유예 기간 |
|
docker-compose ps 명령어를 실행하면, 헬스 체크 결과에 따라 컨테이너 상태가 starting, healthy, unhealthy로 표시된다. 이는 서비스 간의 의존성(depends_on 조건)과 연동되어, 특정 서비스가 정상 상태가 된 후에만 다른 서비스가 시작되도록 보장하는 데 활용될 수 있다.
Docker Compose를 사용한 멀티 컨테이너 환경에서는 각 서비스가 독립적으로 로그를 생성합니다. 효과적인 로그 관리는 애플리케이션의 상태를 모니터링하고 문제를 신속하게 진단하는 데 필수적입니다. 로그 집계는 여러 컨테이너에서 생성된 분산된 로그를 중앙에서 수집, 저장 및 분석할 수 있도록 합니다.
로그 드라이버 설정을 통해 컨테이너의 로그 출력 방식을 제어할 수 있습니다. docker-compose.yml 파일의 서비스 정의 내에서 logging 옵션을 사용하여 이를 구성합니다. 기본적으로 로그는 JSON 파일 형식으로 호스트 머신에 저장되지만, syslog, journald, fluentd, awslogs와 같은 외부 로그 수집 시스템으로 직접 전송하도록 설정할 수 있습니다. 예를 들어, 모든 서비스의 로그를 Fluentd로 전송하려면 다음과 같이 설정할 수 있습니다.
```yaml
services:
web:
image: nginx
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "docker.web"
```
중앙 집중식 로그 관리를 위해서는 ELK 스택(Elasticsearch, Logstash, Kibana)이나 Grafana Loki와 같은 전문 도구를 별도의 서비스로 구성하는 것이 일반적입니다. docker-compose.yml에 로그 수집기와 시각화 도구를 추가하여 통합된 로그 파이프라인을 구축할 수 있습니다. 이를 통해 모든 애플리케이션 로그를 하나의 대시보드에서 검색하고 필터링하며, 실시간으로 오류나 이상 패턴을 감지할 수 있습니다.
도구/기술 | 주요 역할 | 비고 |
|---|---|---|
로그 수집, 파싱, 라우팅 | 경량 로그 수집기 | |
로그 저장 및 인덱싱 | 검색 및 분석 엔진 | |
로그 처리 파이프라인 | 수집, 변환, 출력 | |
로그 시각화 및 대시보드 | Elasticsearch 프론트엔드 | |
로그 집계 시스템 | Grafana와 통합 용이 |
로그 볼륨을 사용하면 컨테이너의 로그 데이터가 호스트의 파일 시스템을 직접 채우는 것을 방지할 수 있습니다. docker-compose 명령어를 사용한 로그 확인은 docker-compose logs [서비스명]으로 특정 서비스의 로그를 실시간으로 스트리밍하거나, -f 옵션을 붙여 지속적으로 팔로우할 수 있습니다. --tail 옵션으로 최근 로그 줄 수만 확인하는 것도 가능합니다.
Docker Compose를 사용한 멀티 컨테이너 환경에서 문제가 발생했을 때는 체계적인 접근이 필요합니다. 일반적인 문제 해결 단계는 컨테이너 상태 확인, 로그 분석, 네트워크 및 의존성 검증 순으로 진행됩니다.
가장 먼저 docker-compose ps 명령어로 모든 서비스의 실행 상태를 확인해야 합니다. Up 상태가 아닌 서비스가 있다면 문제의 시작점이 될 수 있습니다. 특정 서비스의 상세 로그는 docker-compose logs [서비스명] 명령으로 확인할 수 있으며, -f 옵션을 추가하면 실시간 로그 스트림을 따라갈 수 있습니다. 로그에서 error, exception, connection refused, timeout 등의 키워드에 주목해야 합니다. 네트워크 문제가 의심될 경우, docker-compose exec [서비스명] ping [다른서비스명] 명령을 사용해 컨테이너 간 내부 DNS 이름 해석 및 통신 가능 여부를 테스트할 수 있습니다. 또한 depends_on 설정은 컨테이너 실행 순서만 제어할 뿐, 서비스의 실제 준비 상태(healthcheck)를 보장하지 않으므로, 애플리케이션 시작 지연이나 재시도 로직이 필요할 수 있습니다.
보다 복잡한 문제를 해결하기 위한 주요 디버깅 명령어와 접근법은 다음과 같습니다.
상황 | 명령어 또는 접근법 | 설명 |
|---|---|---|
구성 파일 검증 |
|
|
환경 변수 확인 | `docker-compose config | grep -A2 -B2 [변수명]` |
컨테이너 내부 조사 |
| 실행 중인 컨테이너 내부에 접속하여 파일 시스템이나 프로세스를 직접 점검합니다. |
네트워크 상세 정보 |
| Compose가 생성한 네트워크의 세부 정보와 연결된 컨테이너를 확인합니다. |
볼륨 데이터 점검 |
| 볼륨의 물리적 저장 위치와 상태를 확인합니다. |
깨끗한 환경 재시작 |
| 모든 컨테이너와 네트워크를 제거한 후 처음부터 다시 시작하여 일시적 상태 문제를 해결합니다. |
디버깅 시에는 docker-compose up 명령에 --build 옵션을 추가하여 이미지를 재빌드하거나, --force-recreate 옵션으로 컨테이너를 강제 재생성할 수 있습니다. 모든 시도 후에도 문제가 지속된다면, 애플리케이션 코드, Dockerfile의 빌드 지시문, 또는 서비스의 리소스 제한(메모리, CPU) 설정을 재검토해야 합니다.