docker run 명령어가 다섯 줄을 넘어가면 생기는 일
웹 서버 하나, DB 하나, 캐시(Redis) 하나만 있어도 docker run 명령어가 이렇게 늘어납니다.
docker network create my-app-net
docker volume create db-data
docker run -d --name db --network my-app-net -v db-data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16
docker run -d --name cache --network my-app-net redis:7
docker run -d --name web --network my-app-net -p 8080:3000 -e DATABASE_URL=postgresql://db:5432/mydb -e REDIS_URL=redis://cache:6379 my-web-image이걸 매번 손으로 타이핑하는 사람은 없습니다. 껐다 켜는 순서도 신경 써야 하고(DB가 아직 안 떴는데 web이 먼저 접속을 시도하면 실패), 팀원에게 "이 프로젝트 실행하는 법"을 설명하려면 이 명령어들을 그대로 문서에 붙여넣어야 합니다. docker compose는 이 전체를 파일 하나로 선언합니다.
compose.yml — 명령어가 아니라 상태를 선언합니다
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
cache:
image: redis:7
web:
build: .
ports:
- "8080:3000"
environment:
DATABASE_URL: postgresql://db:5432/mydb
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
volumes:
db-data:docker compose up -d이 한 줄이 앞의 5줄짜리 명령어를 대신합니다. docker compose up 을 실행하면 Docker는 이 파일에 정의된 서비스들을 위한 전용 네트워크를 자동으로 만들고, 여기 정의된 볼륨을 만들고, 각 서비스를 컨테이너로 띄우고, depends_on 에 정의된 순서를 지켜 시작합니다. 앞 장에서 다룬 "컨테이너 이름으로 서로를 찾는" DNS도 자동으로 됩니다. web 서비스 안에서 db 라는 이름으로 접속할 수 있는 건 compose가 만든 전용 네트워크 덕분입니다.
여기서 중요한 건 이 파일이 "무엇을 실행하라"는 명령이 아니라 "이런 상태가 되어야 한다"는 선언이라는 점입니다. docker compose up 을 다시 실행하면, 이미 그 상태로 떠 있는 서비스는 건드리지 않고 변경된 부분만 다시 만듭니다.
depends_on의 진짜 의미 — "먼저 뜬다"이지 "준비됐다"가 아닙니다
이 장에서 가장 많은 사람이 놓치는 부분입니다. depends_on: [db] 는 "db 컨테이너를 web보다 먼저 시작한다"는 뜻이지, "db가 접속 요청을 받을 준비가 됐을 때까지 기다린다"는 뜻이 아닙니다. PostgreSQL 컨테이너는 프로세스가 시작된 후에도 초기화 작업을 몇 초간 더 하다가 실제로 접속을 받기 시작합니다. 그 사이에 web 컨테이너가 이미 접속을 시도하면 그냥 실패합니다.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
web:
build: .
depends_on:
db:
condition: service_healthyhealthcheck 를 정의하고 depends_on 에서 condition: service_healthy 를 지정하면, web은 db가 단순히 시작된 것뿐 아니라 헬스체크를 통과할 때까지 기다립니다. 이 설정이 없어서 "가끔 처음 띄울 때만 web이 DB 연결 실패로 죽는다"는 버그를 겪는 경우를 실제로 자주 봅니다. 애플리케이션 코드에서 재연결 로직을 넣는 것도 근본적인 해법이지만, compose 레벨에서 순서를 보장하는 게 가장 간단한 첫 번째 방어선입니다.
자주 쓰는 명령어
docker compose up -d # 정의된 서비스 전부 백그라운드로 실행
docker compose ps # 이 프로젝트의 서비스 상태만 확인
docker compose logs -f web # web 서비스 로그만 실시간으로
docker compose down # 컨테이너와 네트워크 정리 (볼륨은 남음)
docker compose down -v # 볼륨까지 전부 삭제 (데이터 날아감, 주의)docker compose down 과 docker compose down -v 의 차이를 무심코 넘겼다가 로컬 개발 DB 데이터를 통째로 날려본 사람이 저를 포함해 한둘이 아닙니다. -v 플래그는 정말 필요할 때만 붙이세요.
버전에 따라 다른 점 하나: 예전에는 docker-compose(하이픈, 별도 Python 패키지)를 썼지만, 지금은 docker compose(공백, Docker CLI에 내장된 플러그인)가 표준입니다. 온라인 자료 중에는 여전히 하이픈 버전으로 된 예시가 많은데, 최신 Docker를 쓴다면 공백 버전을 쓰면 됩니다. 최상단의 version: "3.8" 같은 버전 선언도 예전 문법의 흔적이고, 최신 Compose Specification에서는 더 이상 필요하지 않습니다.