[도커 30강] 12강. docker compose 시작: 다중 서비스 정의
단일 컨테이너 실습에 익숙해지면 곧바로 부딪히는 문제가 있습니다. 실제 서비스는 보통 웹 애플리케이션, 데이터베이스, 캐시, 워커처럼 여러 프로세스가 함께 움직이기 때문입니다. 이때 docker run 명령을 여러 줄로 수동 관리하면, 순서가 꼬이거나 옵션이 환경마다 달라져서 재현성이 떨어집니다.
docker compose는 이 문제를 해결하기 위한 선언형 도구입니다. “무엇을 실행할지”를 YAML 파일(compose.yaml)에 정의하고, 같은 명령으로 같은 결과를 반복할 수 있게 해줍니다. 오늘은 compose를 처음 시작하는 입장에서, 다중 서비스 정의의 핵심 원리와 실무에서 바로 쓰는 작성 패턴을 차근차근 정리하겠습니다.
핵심 개념
docker compose는 여러 컨테이너(서비스), 네트워크, 볼륨을 하나의 프로젝트 단위로 선언하고 함께 올리고 내리기 위한 도구입니다.- compose 파일의 핵심은
services:입니다. 각 서비스는 이미지, 포트, 환경변수, 볼륨, 네트워크 설정을 가집니다. - 기본적으로 같은 compose 프로젝트의 서비스는 동일 네트워크에 연결되어 서비스 이름으로 서로 통신할 수 있습니다.
docker run이 “명령형(어떻게 실행할지)”에 가깝다면, compose는 “선언형(최종 상태가 무엇인지)”에 가깝습니다.- 실무에서는 compose를 로컬 개발 표준 환경 정의서로 사용하고, 팀 온보딩/테스트 재현/디버깅 속도를 크게 높입니다.
기본 사용
예제 1) 가장 작은 2서비스 compose 만들기
아래 예시는 web(Nginx) + redis 두 서비스를 한 번에 정의합니다. 이 단계 목표는 “다중 서비스를 한 프로젝트로 다루는 감각”을 익히는 것입니다.
mkdir -p ~/docker100/lesson12
cd ~/docker100/lesson12
cat > compose.yaml <<'YAML'
services:
web:
image: nginx:alpine
ports:
- "8082:80"
depends_on:
- redis
redis:
image: redis:7-alpine
YAML
docker compose up -d
docker compose ps
curl -I http://localhost:8082
설명:
services.web와services.redis를 선언하면 compose가 같은 프로젝트 안에서 함께 관리합니다.depends_on은 “생성/시작 순서” 관점의 의존성을 표현합니다(애플리케이션 준비 완료 보장과는 다릅니다).docker compose ps로 상태를 확인하면 개별docker ps보다 프로젝트 기준으로 보기 쉽습니다.
예제 2) 서비스 이름 기반 내부 통신 확인
compose의 강력한 장점은 같은 프로젝트 안에서 DNS가 자동 구성되어 서비스 이름으로 통신된다는 점입니다.
cd ~/docker100/lesson12
docker compose exec web sh -lc "apk add --no-cache curl >/dev/null && curl -s redis:6379 || true"
docker compose exec redis redis-cli ping
docker compose logs --tail=50
설명:
web컨테이너에서redis:6379처럼 서비스 이름으로 접근할 수 있습니다.redis-cli ping결과가PONG이면 Redis 서비스가 정상 기동된 상태입니다.- 장애 분석 시
docker compose logs는 프로젝트 단위 로그를 묶어 보여줘 매우 유용합니다.
예제 3) 볼륨/환경변수를 포함한 실무형 뼈대
초기 학습 단계부터 “상태 저장 데이터는 볼륨으로 분리”하는 습관을 들이면 이후 Postgres/MySQL 같은 상태성 서비스로 확장하기 쉽습니다.
cd ~/docker100/lesson12
cat > compose.yaml <<'YAML'
services:
app:
image: python:3.12-alpine
working_dir: /app
command: sh -c "python -m http.server 8000"
ports:
- "18080:8000"
environment:
APP_ENV: development
APP_PORT: "8000"
volumes:
- ./:/app:ro
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
redis_data:
YAML
docker compose config
docker compose up -d
docker compose down
설명:
docker compose config는 최종 렌더링 결과를 확인하는 필수 점검 명령입니다. 오타/확장 문제를 사전에 발견할 수 있습니다.cache의/data를 named volume으로 분리해 컨테이너 교체와 데이터 생명주기를 분리했습니다.down으로 정리해도 named volume은 기본적으로 남기 때문에, 데이터 유지/삭제 정책을 의식적으로 관리해야 합니다.
자주 하는 실수
실수 1) compose 파일 이름/경로를 제멋대로 두고 “명령이 안 된다”고 판단
- 원인: 기본 파일명(
compose.yaml,docker-compose.yml) 규칙을 모르거나 잘못된 디렉터리에서 실행. - 해결: 프로젝트 루트에
compose.yaml을 두고 그 디렉터리에서docker compose up -d를 실행합니다. 필요하면-f <파일>을 명시합니다.
실수 2) depends_on이 서비스 준비 완료까지 보장한다고 오해
- 원인: 시작 순서(start order)와 readiness(준비 완료) 개념을 혼동.
- 해결: 앱에서 재시도 로직을 넣거나 healthcheck/대기 스크립트를 조합해 “진짜 준비됨”을 판단합니다. 이 주제는 다음 강의에서 더 깊게 다룹니다.
실수 3) 호스트 포트 충돌을 무시하고 계속 같은 포트 사용
- 원인: 팀원/기존 서비스와 포트 정책을 공유하지 않음.
- 해결: 로컬 포트 컨벤션(예: API 18xxx, Admin 19xxx)을 문서화하고
docker compose ps로 실제 매핑을 항상 확인합니다.
실수 4) 상태 데이터와 애플리케이션 코드를 같은 마운트 전략으로 처리
- 원인: “일단 전부 바인드 마운트”로 시작해 운영 시 데이터 관리가 꼬임.
- 해결: 코드(개발 편의)는 bind mount, 상태 데이터(DB/캐시)는 named volume으로 분리하는 원칙을 지킵니다.
실무 패턴
compose를 잘 쓰는 팀은 파일을 “개발 환경 계약서”처럼 다룹니다. 누가 실행해도 같은 서비스가 같은 방식으로 뜨고, 필요한 의존성이 누락되지 않아야 합니다. 즉, compose 파일은 단순 실행 스크립트가 아니라 팀 공통 실행 표준입니다.
실무에서 자주 쓰는 패턴은 다음과 같습니다.
첫째, 서비스 역할을 분리합니다. 예를 들어 app, db, cache, worker를 분명히 나누고, 공개 포트는 정말 필요한 서비스만 엽니다. 이 습관은 보안 노출면을 줄이고 운영 전환 때 리버스 프록시 구조로 확장하기 쉽게 만듭니다.
둘째, 환경변수는 compose에 하드코딩하지 않고 .env 또는 외부 주입으로 옮깁니다. 오늘은 입문이라 environment:를 직접 넣었지만, 팀 프로젝트에서는 비밀번호/토큰 같은 민감 정보를 repo에 남기지 않는 규칙을 반드시 세워야 합니다.
셋째, 로그/상태 확인 루틴을 고정합니다. 장애가 나면 보통 “어느 컨테이너가 먼저 실패했는지”부터 확인해야 하므로, docker compose ps, docker compose logs -f, docker compose events를 팀 표준 디버깅 절차로 문서화하면 대응 속도가 빨라집니다.
넷째, compose 파일 리뷰 항목을 운영합니다. 코드 리뷰처럼 “새 포트가 열렸는가?”, “데이터 볼륨이 분리되었는가?”, “불필요한 privileged 옵션이 없는가?”를 체크하면, 환경 기술 부채가 초기에 통제됩니다.
마지막으로, compose는 단순히 로컬 개발에만 쓰는 도구가 아닙니다. CI의 통합 테스트 환경에서도 compose를 사용하면 애플리케이션+의존 서비스(예: Redis, DB)를 재현 가능한 형태로 띄울 수 있어 테스트 신뢰도를 높일 수 있습니다. 즉, “내 노트북에서 되는 설정”이 아니라 “어디서나 반복 가능한 설정”으로 사고를 전환하는 것이 핵심입니다.
오늘의 결론
한 줄 요약: docker compose는 다중 서비스를 사람이 기억으로 조립하는 방식에서, 선언으로 재현하는 방식으로 전환해주는 기본 도구다.
단순히 컨테이너를 여러 개 띄우는 것이 목표가 아니라, 팀이 같은 방법으로 같은 환경을 반복할 수 있게 만드는 것이 compose 학습의 진짜 목적입니다.
연습문제
web + redis2서비스 compose를 직접 작성하고,docker compose up -d후docker compose ps출력에서 각 서비스 상태와 포트 매핑을 설명해보세요.web서비스에서redis서비스로 서비스명(redis) 기반 접근이 가능한지 확인하고, 왜 IP 대신 이름을 쓰는 것이 유지보수에 유리한지 정리해보세요.cache서비스에 named volume을 붙였다가 컨테이너를 재생성한 뒤 데이터가 유지되는지 검증해보세요. 유지된다면 그 이유를 데이터 생명주기 관점에서 설명해보세요.
이전 강의 정답
11강 연습문제 해설:
-p 8082:80은 호스트 8082를 컨테이너 80으로 전달하는 고정 매핑입니다.docker port와curl http://localhost:8082로 즉시 검증할 수 있습니다. 반면-p 80처럼 호스트 포트를 생략하면 Docker가 임의 포트를 배정하므로 병렬 테스트에는 유리하지만, 문서화/접속 경로 고정에는 불리할 수 있습니다.lesson11_net같은 사용자 정의 네트워크에서 컨테이너를 실행하면 Docker DNS가 서비스/컨테이너 이름을 해석합니다. 따라서http://api:포트형태 호출이 가능하며, 이는 IP 하드코딩보다 교체/재배포 시 훨씬 안정적입니다.- 같은 포트여도 앱이
127.0.0.1에만 바인딩하면 컨테이너 내부 루프백에서만 수신하여 호스트 접근이 실패할 수 있습니다.0.0.0.0으로 바인딩하면 컨테이너 네트워크 인터페이스 전체에서 수신하므로 publish된 포트를 통해 외부 접근이 가능해집니다.
실습 환경/재현 정보
- OS: macOS 15+ (Apple Silicon) 또는 Ubuntu 22.04+
- Docker 버전: Docker Desktop/Engine 25.x 이상
- Compose 버전: Docker CLI plugin compose v2.x (
docker compose version) - 사용 이미지:
nginx:alpine,redis:7-alpine,python:3.12-alpine - 실행 순서:
- 프로젝트 디렉터리 생성 및
compose.yaml작성 docker compose config로 설정 렌더링 점검docker compose up -d로 다중 서비스 기동docker compose ps/logs/exec로 상태와 통신 확인docker compose down으로 정리
- 프로젝트 디렉터리 생성 및
- 재현 체크:
- 서비스가 모두
Up상태인가? - 공개 포트가 의도한 서비스에만 설정되었는가?
- 서비스명 기반 내부 통신이 되는가?
- 상태 데이터가 named volume으로 분리되어 있는가?
- 팀원이 동일 명령으로 같은 결과를 재현할 수 있는가?
- 서비스가 모두