본문 바로가기
잡(job)기술

도커 헬스체크, Up 상태만 믿고 있지 않은가?

by 무니이구나 2026. 2. 1.

들어가며: “전부 초록불인데 왜 안 되지?”

docker-compose up으로 멀티 컨테이너 애플리케이션을 실행했는데, docker ps를 보면 모든 컨테이너가 Running 상태다. 하지만 애플리케이션 로그에는 데이터베이스 연결 실패 오류가 쌓인다. 겉으로 보면 전부 정상이다. 하지만 실제 서비스는 동작하지 않는다. 이 문제는 아주 흔한 오해에서 시작된다. 컨테이너가 실행 중이라는 것과, 서비스가 준비되었다는 것은 전혀 다른 의미다. 이 차이를 메우기 위해 도커에는 healthcheck 기능이 있다. 이 글에서는 도커 헬스체크를 제대로 이해하기 위해 반드시 알아야 할 4가지 핵심을 정리한다.


1. Running과 Healthy는 완전히 다른 상태다

가장 먼저 짚어야 할 점은,
컨테이너 상태 Running과 Healthy는 별개의 개념이라는 것이다.

Running

  • 컨테이너의 주 프로세스가 실행 중
  • 내부 서비스 준비 여부는 고려하지 않음
  • 프로세스만 살아 있으면 Running

Healthy

  • healthcheck 명령이 성공적으로 통과
  • 서비스가 실제로 요청을 받을 준비가 되었음

상태 비교 표

항목 Running Healthy
프로세스 실행 O O
포트 열림 보장 안 됨 확인됨
서비스 준비 완료 X O
의존성 조건 사용 X O

 

도커는 헬스체크 결과를 다음 세 가지 상태로 표시한다.

상태 의미
starting 컨테이너 시작 후 첫 성공 전
healthy 헬스체크 성공
unhealthy 연속 실패

 

이 정보는 docker ps, docker inspect로 확인할 수 있다.


2. 도커는 글자를 읽지 않는다, 종료 코드만 본다

헬스체크에서 가장 많이 오해하는 부분이다. 도커는 헬스체크 명령이 출력하는 문자열을 해석하지 않는다. 도커가 확인하는 것은 오직 하나다.

명령의 종료 코드(exit code)

 

도커 공식 문서 기준은 명확하다.

The health check is considered successful if the command exits with a status code 0.
출처: Docker 공식 문서 – Healthcheck

 

종료 코드 규칙

종료 코드의미

종료 코드 의미
0 성공 (healthy)
0 이외 실패

 

아래 메시지는 도커에게 아무 의미가 없다.

localhost:5432 - accepting connections

 

사람에게만 의미가 있다. 도커는 마지막에 반환된 숫자만 본다. 이 방식은 문자열 파싱보다 안정적이며, 자동화 환경에 적합하다.


3. pg_isready는 문지기일 뿐이다

PostgreSQL 헬스체크에 가장 널리 쓰이는 도구가 pg_isready다. PostgreSQL에서 공식 제공하며, 추가 설치가 필요 없다.

pg_isready가 확인하는 것

  • PostgreSQL 서버 프로세스 실행 여부
  • TCP 또는 Unix 소켓 연결 가능 여부
  • 인증 단계 도달 가능 여부

확인하지 않는 것

  • 스키마 존재 여부
  • 마이그레이션 완료 여부
  • 쿼리 실행 가능 여부

즉, pg_isready는 문지기 역할이다. 문이 열려 있는지는 확인하지만, 내부 준비 상태는 알 수 없다.

pg_isready 종료 코드 의미

종료 코드 의미
0 연결 가능
1 서버 응답은 있으나 연결 불가
2 연결 시도 실패
3 잘못된 인자 등 내부 오류

 

직접 확인 예시

$ pg_isready
localhost:5432 - accepting connections
$ echo $?
0
$ pg_isready
localhost:5432 - no response
$ echo $?
2

 

도커는 메시지를 무시하고 0과 2만 본다.


4. 진짜 핵심은 condition: service_healthy다

헬스체크의 진짜 위력은 docker-compose에서 드러난다.

depends_on:
  postgres:
    condition: service_healthy

 

이 설정은 서비스 시작 순서를 상태 기준으로 제어한다.

타임라인으로 보면

시간 DB 컨테이너 앱 컨테이너
0초 시작 시작
2초 초기화 중 실행
5초 준비 완료 대기
6초 healthy 실행 시작

 

헬스체크가 없다면 앱은 준비되지 않은 DB에 즉시 연결을 시도한다. 결과는 연결 실패와 재시작이다.

condition: service_healthy를 사용하면,

  • DB가 실제로 준비될 때까지 앱은 대기
  • 불필요한 sleep, 재시도 코드 제거 가능

실전 예제: 제대로 된 헬스체크 설정

PostgreSQL 헬스체크 예시

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 5s
      timeout: 3s
      retries: 5

 

더 엄격한 헬스체크 예시 (쿼리 기반)

healthcheck:
  test: ["CMD-SHELL", "psql -U user -d postgres -c 'SELECT 1' > /dev/null"]
  interval: 5s
  timeout: 3s
  retries: 5

 

이 방식은 실제 쿼리 실행 가능 여부까지 확인한다.


마무리: 겉모습이 아니라 진짜 상태를 봐야 한다

Running은 단순히 프로세스가 살아 있다는 신호다. 서비스가 준비되었다는 보장은 아니다. 핵심을 다시 정리하면 다음과 같다.

  1. Running과 Healthy는 다르다
  2. 도커는 종료 코드만 본다
  3. pg_isready는 연결 가능성만 확인한다
  4. service_healthy로 시작 순서를 제어해야 한다

이제 질문을 이렇게 바꿔야 한다.

“컨테이너가 떠 있나?”가 아니라
“서비스는 정말 준비됐나?”