Docker Compose
여러 개의 컨테이너를 모아서 하나의 애플리케이션 서비스로 정의하고, 한 번에 묶어서 관리(실행, 종료 등)할 수 있게 도와주는 도구
# 일반 컨테이너 실행(docker run) vs 컴포즈 실행(docker compose up)
| 구분 | 일반 컨테이너 실행 (docker run) | 컴포즈 실행 (docker compose up) |
| 관리 방식 | 명령어를 터미널에 매번 직접 입력 | docker-compose.yml 파일로 저장하여 관리 |
| 실행 대상 | 단일(1개) 컨테이너 실행 위주 | 여러 개(다중) 컨테이너를 동시에 실행 |
| 네트워크 구성 | 컨테이너끼리 연결하려면 설정을 매번 추가해야 함 | 같은 파일에 있으면 자동으로 내부 네트워크 연결 |
| 휴먼 에러 | 오타나 옵션 누락 가능성이 매우 높음 | 설정이 파일로 고정되어 있어 실행 결과가 항상 일정함 |
| 종료 및 삭제 | docker stop 후 docker rm을 각각 해줘야 함 | docker compose down 명령어 하나로 깔끔하게 정리 |
# 버전 차이 v1 : v2
Docker Compose v1 vs v2 전체 차이점 비교
| 구분 | Docker Compose v1 (Legacy) | Docker Compose v2 (Modern) |
| 명령어 형식 | 하이픈(-) 사용 docker-compose up |
띄어쓰기( ) 사용 docker compose up |
| 설치 방법 | OS별로 GitHub에서 바이너리를 별도 다운로드 후 경로(PATH) 설정 필요 | Docker Desktop 설치 시 기본 플러그인으로 자동 포함 (따로 설치 필요 없음) |
| 개발 언어 | Python (파이썬) | Go (고언어) |
| 실행 방식 | Docker 엔진과 분리된 독립형 도구 | Docker CLI의 정식 서브 명령어(플러그인)로 통합 |
| 속도 및 성능 | 파이썬 런타임을 거치므로 상대적으로 느림 | Go 언어 기반 및 Docker 엔진 직접 통신으로 대폭 향상 |
| 주요 추가 기능 | 컨테이너 간 파일 복사 시 docker cp와 컨테이너 ID 필요 | docker compose cp 명령어로 서비스 이름 기반 파일 복사 지원 |
| 지원 상태 | 2023년 7월부로 지원 종료 (Deprecated) | 현재 표준 버전이며 지속적인 기능 업데이트 중 |
YAML 파일 구조 및 문법 차이점 비교
| 구분 | Docker Compose v1 (구버전 사양) | Docker Compose v2 (신버전 사양) |
| version 필드 | 파일 최상단에 필수로 명시 (version: '3' 등) | 생략 권장 (Deprecated) / 넣지 않는 것이 표준 |
| 호환성 제약 | 파일에 적힌 version과 설치된 Docker 엔진 버전이 맞지 않으면 에러 발생 | 사양이 통합되어 엔진 버전에 맞게 자동으로 최적의 기능 제공 |
| 볼륨/네트워크 정의 | 컨테이너 내부나 로컬 호스트 기준의 단순 매핑 위주 | long syntax(상세 문법) 지원으로 권한, 읽기전용 등 세부 제어 가능 |
| 리소스 제한 (CPU/메모리) | v3 초기에는 deploy 속성 안에서만 제한 가능 (Swarm 모드용) | Swarm 모드가 아니더라도 일반 컨테이너 리소스 제한을 일관되게 적용 |
# docker-compose.yml
Docker Compose의 핵심은 docker-compose.yml이라는 단 하나의 설정 파일에 있습니다. 이 파일에 "내가 실행할 컨테이너들은 이것들이고, 포트와 환경 변수는 이렇게 세팅해 줘"라고 서류로 미리 적어두는 것
version 3.8 # 프로그램이 읽는 설계도(YAML)의 버전(v1에는 필수, v2에서는 권장 X)
services:
mariadb-host: # 서비스 이름
image: mariadb:10.6 # Docker Hub에서 MariaDB 10.6 버전을 가져옵니다.
container_name: mariadb-host2 # Docker Desktop이나 터미널에 표시될 실제 이름입니다.
networks:
- my-network2 # 이 컨테이너를 가상 네트워크망에 가입시킵니다.
ports:
- "3311:3306" # 외부(SQLyog 등)에서 접속할 때는 3311 포트를 사용합니다.
environment:
MARIADB_ROOT_PASSWORD: "1234" # DB의 root 계정 비밀번호를 1234로 세팅합니다.
volumes:
- ./mariadb_data:/var/lib/mysql # 컨테이너를 지워도 데이터가 보존되도록 내 컴퓨터 폴더와 연동합니다.
restart: always # [중요] 컨테이너가 에러로 꺼지거나 도커가 재시작되면 자동으로 다시 켭니다.
was-host: # 서비스 이름
image: s3458600/my-flask-app:1.0 # [username]/[repo]:[tag] 규칙에 맞게 Docker Hub에 올렸던 내 Flask 이미지를 가져옵니다.
build: ./build
# context: ./build
# dockerfile: Dockerfile
container_name: was-host2 # 컨테이너 실제 이름을 was-host2로 지정합니다.
networks:
- my-network2 # DB와 같은 네트워크망에 입주시켜 소통이 가능하게 만듭니다.
ports:
- "5011:5000" # 웹 브라우저 주소창에 localhost:5011을 치면 이 Flask 웹 서버로 접속됩니다.
volumes:
- .:/app # [꿀팁 옵션] 현재 내 컴퓨터 폴더(.)와 컨테이너 내부(/app)를 실시간 동기화합니다.
# 이러면 소스코드를 수정했을 때 컨테이너를 재빌드하지 않아도 바로 반영됩니다.
restart: always # 웹 서버가 죽어도 도커가 알아서 자동으로 다시 살려냅니다.
depends_on:
- mariadb-host # [핵심] db가 먼저 완전히 켜진 다음 이 Flask 웹 서버가 실행되도록 순서를 보장합니다.
nginx-host: # 서비스 이름
image: nginx:stable # Nginx의 안정화(stable) 버전을 Docker Hub에서 가져옵니다.
container_name: nginx-gateway # 컨테이너의 실제 이름을 지정합니다. (로그 확인 시 편리합니다.)
networks:
- my-network2 # Flask 및 DB와 같은 가상 네트워크망에 입주시켜 통신을 가능하게 합니다.
ports:
- "80:80" # 외부(브라우저)에서 80번 포트로 들어오는 요청을 이 컨테이너로 전달합니다.
volumes:
# [핵심] 호스트의 설정파일을 컨테이너 내부의 Nginx 설정 경로로 덮어씌웁니다.
# :ro는 read-only(읽기 전용) 옵션으로, 컨테이너가 설정 파일을 임의로 수정하지 못하게 막습니다.
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- was-host # [중요] Flask 서버가 먼저 준비된 후 Nginx가 실행되도록 순서를 보장합니다.
networks: # 이 두방을 하나로 연결해 주는 전용 통신선(브리지)
my-network2:
driver: bridge
./nginx/default.conf 파일
server {
listen 80;
server_name wsl-docker1.codefoilo.store;
location / {
proxy_pass http://was-host:5000;
proxy_set_header Host $http_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_redirect off;
}
}
# proxy_set_header ... (헤더 전달)
# 웹 서버와 WAS 사이에서 사용자의 실제 정보를 유지해 주는 역할을 합니다.
# Host $http_host;: 사용자가 처음에 입력한 도메인 주소를 그대로 전달합니다.
# X-Real-IP $remote_addr;: 사용자의 실제 IP를 Flask 서버에 알려줍니다.
# X-Forwarded-For ...: 프록시를 거치면서 바뀐 IP 경로를 기록하여, Flask 앱이 실제 접속자를 알 수 있게 합니다.
# proxy_redirect off;: Nginx가 서버 응답의 위치 정보를 임의로 바꾸지 못하게 하여 원본 응답을 그대로 전달
1. '컨테이너 이름'과 '서비스 이름'의 차이
- mariadb-host (서비스 이름): 도커 컴포즈 안에서 쓰이는 역할(직책) 이름입니다. "이 녀석은 우리 시스템에서 '데이터베이스 엔진' 역할을 담당해"라고 지정하는 것입니다.
- container_name: mariadb-host2 (컨테이너 이름): 리눅스 운영체제나 Docker Desktop 전체에서 식별하기 위한 실제 컴퓨터(프로세스) 이름입니다.
💡 왜 굳이 분리할까요? (도커 컴포즈의 진짜 목적)
도커 컴포즈의 강력함은 "스케일 아웃(Scale-out, 컨테이너 늘리기)"을 할 때 드러납니다.
사용자가 갑자기 몰려서 Flask 웹 서버(was-host)를 3개로 늘려야 한다고 가정해 봅시다. 일반 도커라면 docker run 명령어를 이름만 다르게 해서 3번 입력해야 합니다.
하지만 도커 컴포즈에서는 명령어 한 줄로 서비스를 복사할 수 있습니다.
# Docker-Compose 핵심 명령어
1. 기존 명령어 보완 및 꿀팁
docker compose up -d --build
- 소스 코드를 수정(예: Flask의 app.py 변경 등)한 후 재실행할 때 매우 자주 씁니다. --build를 안 붙이면 소스 코드가 바뀌어도 기존에 만들어진 옛날 이미지를 그대로 써서 변경 사항이 반영되지 않으니 주의해야 합니다.
📝 도커 컴포즈 이미지 빌드 핵심 정리
1. 빌드의 근본 원리
컴포즈로 빌드(up --build 또는 build)를 하더라도, 내부적으로는 일반 도커(docker build)와 똑같이 도커 엔진(도커 데스크탑)에 정식 이미지가 생성됩니다.컴포즈는 '이미지 빌드 ➡️ 도커 데스크탑에 등록 ➡️ 컨테이너 실행'이라는 번거로운 과정을 명령어 한 줄로 대신해 주는 역할입니다.
2. 소스 코드 수정 후 재빌드 시 일어나는 일
도커 이미지는 한 번 만들어지면 중간에 수정할 수 없는 수정 불가능(Immutable) 사양입니다.따라서 코드를 고치고 다시 빌드하면, 기존 이미지를 수정하는 것이 아니라 최신 코드가 반영된 '완전히 새로운 이미지'를 생성합니다.
3. <none> (댕글링) 이미지의 탄생
도커에서는 동일한 이름(태그)으로 이미지를 다시 빌드하면, 새로 만든 이미지에게 이름을 빼앗기는 현상이 발생합니다.이름을 빼앗긴 원래의 옛날 이미지: 이름이 없어져 리스트에 <none>:<none> 상태로 남게 됩니다. (이를 유령/댕글링 이미지라고 부릅니다.)새로 빌드된 최신 이미지: 원래 가고 있던 이름(태그)을 차지하고, 컴포즈는 이 최신 이미지를 바라보고 컨테이너를 실행합니다.
docker compose down -v
- ⚠️ 주의: -v 옵션은 데이터베이스의 데이터(DB 데이터)까지 싹 다 날려버립니다. 개발 중에 DB 스키마가 꼬여서 완전히 초기화하고 싶을 때만 조심해서 사용하세요.
docker compose logs <서비스명>
- 여기에 -f (follow) 옵션을 붙여서 docker compose logs -f web처럼 쓰면, 터미널을 끄지 않고 실시간으로 서버에 찍히는 로그(에러 메시지나 프린트문)를 계속 모니터링할 수 있어 디버깅할 때 필수입니다.
2. 무조건 알아야 하는 필수 추가 명령어 5선
① 컨테이너 내부로 접속하기 (exec)
실행 중인 컨테이너 안으로 들어가서 리눅스 명령어를 입력하거나 데이터베이스에 직접 접속하고 싶을 때 사용합니다.
docker compose exec <서비스명> bash
# 만약 컨테이너에 bash가 없다면 sh 사용
docker compose exec <서비스명> sh
- 예시: docker compose exec db mysql -u root -p를 치면 컨테이너 내부의 MySQL 콘솔로 바로 진입할 수 있습니다.
② 설정 파일(yml) 문법 검사 및 최종 확인 (config)
내가 작성한 docker-compose.yml 파일에 오타가 없는지, 환경 변수 처리가 올바르게 되었는지 실행하기 전에 미리 검사해 줍니다.
docker compose config
- 오타를 찾아줄 뿐만 아니라, 생략된 기본값(Default) 설정까지 모두 포함된 "Docker가 실제로 해석한 최종 조립도"를 화면에 출력해 주므로 검증하기 좋습니다.
③ 컨테이너 안에서 딱 일회성 명령어만 실행하기 (run)
전체 서비스를 다 켜는 게 아니라, 특정 컨테이너를 이용해 딱 한 번만 마이그레이션이나 초기화 스크립트를 실행하고 싶을 때 씁니다.
docker compose run --rm <서비스명> <명령어>
- 예시: 파이썬 컨테이너에서 DB 테이블 생성 스크립트만 실행하고 빠져나오고 싶을 때: docker compose run --rm web python manage.py db upgrade
- --rm 옵션을 붙여주면 일회성 명령이 끝나고 나서 찌꺼기 컨테이너를 자동으로 삭제해 줍니다.
④ 실시간 리소스 사용량 확인 (top / stats)
컨테이너들이 내 컴퓨터의 CPU와 메모리를 얼마나 잡아먹고 있는지, 어떤 프로세스가 돌고 있는지 확인할 때 씁니다.
# 특정 서비스의 내부 프로세스 조회
docker compose top <서비스명>
# 전체 컨테이너의 CPU/메모리 실시간 사용량 모니터링 (Docker 기본 명령어 활용)
docker stats
⑤ 컨테이너와 로컬 컴퓨터 간 파일 복사 (cp)
컨테이너 내부에 있는 로그 파일을 내 컴퓨터로 가져오거나, 내 컴퓨터에 있는 설정 파일을 실행 중인 컨테이너 안으로 던져 넣을 때 사용합니다. (v2 전용 명령어)
# 컨테이너 안의 파일을 내 로컬 컴퓨터로 복사해오기
docker compose cp <서비스명>:/app/logs.txt ./local_logs.txt
# 내 로컬 파일을 컨테이너 안으로 집어넣기
docker compose cp ./config.json <서비스명>:/app/config.json
느낀점
오늘은 도커 컴포즈에 대해 배웠다. 개념 자체는 어렵지 않았지만 정작 실습하려고 할 때 DB 연결이 안되서 굉장히 헤맸다. 도커로 실습할 때 이미지를 만들고 빌드하면서 처음부터 해왔기 때문에 설정이 한번 꼬이면서 어디서부터 잘못 된 것인지 원인이 뭔지 파악하는 것도 쉽지 않았다. 이 부분은 도커를 계속 사용해보면서 익히는 것 외에는 할 수 있는 게 없는 것같다.....
——————————————————————————
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
'학습일지' 카테고리의 다른 글
| [스나이퍼팩토리] 한컴AI아카데미(26.06.04) urllib (0) | 2026.06.04 |
|---|---|
| [스나이퍼팩토리] 한컴AI아카데미(26.06.02) GitHub CI/CD (0) | 2026.06.04 |
| [스나이퍼팩토리] 한컴AI아카데미(26.05.28) Docker Hub (0) | 2026.05.28 |
| [스나이퍼팩토리] 한컴AI아카데미(26.05.27) Docker (0) | 2026.05.27 |
| [스나이퍼팩토리] 한컴AI아카데미(26.05.22) GitHub (0) | 2026.05.23 |