
Docker Compose에서 서비스 재시작 순서가 꼬이는 원인은 대부분 depends_on을 오해하는 데서 시작됩니다. depends_on은 컨테이너를 “먼저 실행”만 시켜줄 뿐, 그 안의 애플리케이션이 요청을 받을 준비가 됐는지는 전혀 확인하지 않습니다. 이 글은 docker compose v2(CLI 플러그인) 기준으로, 어디까지가 자동으로 보장되고 어디서부터 직접 챙겨야 하는지를 실제 설정 예시로 정리합니다.
depends_on이 보장하는 건 순서일 뿐, 준비 상태가 아닙니다
depends_on에 서비스 이름만 나열하는 짧은 문법을 쓰면, Compose는 해당 컨테이너의 프로세스가 “실행됐다(running)”는 것만 확인하고 다음 컨테이너를 시작합니다. PostgreSQL이나 MySQL 같은 DB 이미지는 컨테이너 프로세스가 뜬 뒤에도 초기화 스크립트를 몇 초간 더 돌리기 때문에, 이 시점에 연결을 시도하는 애플리케이션 컨테이너는 접속 거부 에러를 만나기 쉽습니다.
문제는 이 실패가 항상 재현되지는 않는다는 점입니다. 로컬 SSD처럼 DB 초기화가 빠른 환경에서는 우연히 타이밍이 맞아 정상 동작하다가, 노트북 성능이 낮거나 볼륨 I/O가 느린 서버에서만 API 컨테이너가 죽어버리는 식으로 나타납니다.
docker compose restart가 의존성 그래프를 무시하는 함정
더 자주 걸리는 함정은 따로 있습니다. docker compose up은 depends_on 순서를 지키지만, docker compose restart <서비스명>은 지정한 컨테이너 하나만 재시작할 뿐 의존성 그래프를 다시 계산하지 않습니다. 즉 API 컨테이너만 콕 집어 재시작하면 DB가 살아있든 말든 그대로 진행됩니다.

실무에서 자주 겪는 순서는 이렇습니다. DB 컨테이너가 메모리 부족으로 죽었다가 restart: always 정책 덕분에 스스로 살아나는 동안, 이미 연결이 끊겨 죽어버린 API 컨테이너는 재시작 횟수 제한(on-failure 재시도 횟수)을 넘겨 그대로 exited 상태로 멈춰 있습니다. depends_on은 최초 up 시점의 순서만 챙길 뿐, 실행 중 DB가 다시 살아났다고 API를 자동으로 재기동시켜 주지 않습니다.
# db만 죽었다가 살아난 상황
$ docker compose ps
NAME STATUS
myapp-db-1 Up 2 minutes (healthy)
myapp-api-1 Exited (1) 90 seconds ago
# api만 지정해서 재시작 - db 상태는 확인하지 않음
$ docker compose restart api
[+] Restarting 1/1
✔ Container myapp-api-1 Started 0.4s
condition: service_healthy로 실제 준비 상태까지 기다리기
Compose Specification(도커 컴포즈 v2 기준, version: 필드가 없어도 동작하는 통합 스펙)에서는 depends_on을 긴 문법으로 써서 condition 값을 지정할 수 있습니다. service_started(기본값), service_healthy, service_completed_successfully 세 가지 중 실무에서 가장 유용한 건 service_healthy이며, 대상 서비스에 healthcheck 블록이 정의돼 있어야만 동작합니다.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
restart: unless-stopped
api:
build: .
depends_on:
db:
condition: service_healthy
restart: on-failure
이렇게 설정하면 docker compose up -d 실행 시 db 컨테이너가 healthy 상태로 전환될 때까지 api 컨테이너 시작을 미룹니다.
$ docker compose up -d
[+] Running 2/2
✔ Container myapp-db-1 Healthy 6.2s
✔ Container myapp-api-1 Started 6.3s
여기서 healthcheck를 공식 레퍼런스의 정의대로 test, interval, retries, start_period 항목을 갖춰 작성하지 않으면, Compose가 파일을 파싱하는 단계에서부터 condition: service_healthy를 거부합니다. 이미지에 curl이나 pg_isready 같은 점검용 바이너리가 없는 슬림 이미지라면 healthcheck 명령 자체를 실행할 수 없다는 점도 미리 확인해야 합니다.

이 방법이 통하지 않는 경우는 언제일까요?
condition: service_healthy는 최초 기동 시점의 순서 문제만 해결합니다. 도커 컨테이너가 이미 실행 중인 상태에서 DB가 잠깐 응답 불가 상태가 됐다가 돌아오는 상황은 이 설정과 무관하며, 이때 API가 스스로 재접속을 시도하도록 애플리케이션 레벨에서 커넥션 재시도 로직을 넣어야 합니다.
또한 healthcheck의 interval과 retries 값을 크게 잡을수록 전체 스택이 healthy로 판정되기까지 대기 시간이 늘어나므로, CI 파이프라인처럼 기동 속도가 중요한 환경에서는 트레이드오프가 생깁니다. 그리고 구버전 docker-compose(파이썬으로 작성된 v1, 도커 공식적으로 지원이 종료된 버전)나 Compose 파일 version: '3' 계열 일부에서는 이 조건부 문법이 무시되거나 에러가 나므로, 반드시 docker compose version으로 v2 CLI 플러그인을 쓰고 있는지 먼저 확인해야 합니다.
재시작 전에 이 체크리스트부터 확인해 보세요
세 가지 설정 방식의 차이를 표로 정리하면 다음과 같습니다.
| 구분 | 짧은 문법(list) | condition: service_started | condition: service_healthy |
|---|---|---|---|
| 보장 범위 | 시작 순서만 | 시작 순서만(명시적 표기) | 헬스체크 통과까지 대기 |
| 필요 조건 | 없음 | 없음 | 대상 서비스 healthcheck 필수 |
| docker compose restart 적용 여부 | 적용 안 됨 | 적용 안 됨 | 적용 안 됨 |
| 지원 환경 | 모든 버전 | Compose Specification / v2 | Compose Specification / v2 |
표에서 보듯 세 방식 모두 docker compose restart 명령에는 적용되지 않는다는 공통점이 있습니다. 특정 서비스 하나만 재시작해야 할 때 의존성까지 함께 점검하고 싶다면, docker compose stop <서비스> && docker compose up -d <서비스>처럼 up 계열 명령으로 다시 태우는 편이 안전하고, 애플리케이션 쪽에는 DB 재연결 로직을 넣어 depends_on이 커버하지 못하는 실행 중 장애까지 대비해 두는 것이 실제 운영에서 재시작 순서가 꼬이는 사고를 줄이는 방법입니다.
