docker compose up을 하면 앱 컨테이너가 DB보다 먼저 떠서 connection refused로 죽는 경우가 있습니다. depends_on만 적으면 컨테이너 "시작 순서"는 지켜지지만, DB 프로세스가 연결을 받을 준비가 됐는지는 보장하지 않습니다. 컨테이너가 떴다고 안의 서비스가 준비된 것은 아니기 때문입니다. 이걸 해결하는 게 healthcheck입니다.
핵심 — "시작"과 "준비"는 다르다
depends_on: [db]는 db 컨테이너가 시작되면 곧바로 앱을 띄웁니다. 하지만 PostgreSQL은 컨테이너 시작 후에도 수 초간 초기화를 합니다. 그 사이 앱이 붙으면 실패합니다. 해법은 db에 헬스체크를 달고, 앱이 "db가 healthy일 때까지" 기다리게 하는 것입니다.
1단계 — DB에 healthcheck 추가
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
pg_isready는 PostgreSQL이 연결을 받을 준비가 됐을 때만 종료 코드 0을 돌려줍니다. start_period는 기동 유예 구간으로, 이 시간 안의 실패는 재시도 횟수에 포함되지 않습니다.
2단계 — depends_on에 condition 걸기
app:
image: myapp:latest
depends_on:
db:
condition: service_healthy
service_healthy를 명시하면 Compose는 db가 healthy가 될 때까지 app을 띄우지 않습니다. 이것이 빠진 depends_on: [db]와의 결정적 차이입니다.
3단계 — 상태 확인
docker compose up -d
docker compose ps # STATUS에 (healthy) 표시
docker inspect --format '{{.State.Health.Status}}' <container>
docker compose ps의 STATUS가 Up 12 seconds (healthy)로 바뀌면 헬스체크가 동작하는 것입니다. (health: starting)은 아직 start_period 안이라는 뜻입니다.
자주 만나는 함정
테스트 명령이 컨테이너 안에 없으면 헬스체크가 항상 실패합니다.
container is unhealthy
dependency failed to start: container app-db-1 is unhealthy
curl로 헬스체크를 짰는데 이미지에 curl이 없으면 이 에러가 납니다. 해당 이미지에 실제 존재하는 도구(pg_isready, wget, nc)를 쓰거나 CMD-SHELL로 셸 내장 기능을 활용하세요. 또 retries와 interval을 너무 짧게 잡으면 느린 DB가 영원히 unhealthy로 남을 수 있으니 start_period를 넉넉히 줍니다.
적용 체크리스트
docker compose config # condition·healthcheck 문법 검증
docker compose ps # (healthy) 표시 확인
docker compose logs db # 헬스체크 실패 시 원인 추적
healthcheck + service_healthy 조합 하나로 "DB 먼저, 앱 나중" 기동 순서 문제의 대부분이 사라집니다.
Compose 헬스체크를 직접 짜고 일부러 실패시켜 (unhealthy)를 관찰하는 실습은 도커 트랙에서 무료로 해볼 수 있습니다.