빌드노트
Compose에 depends_on을 넣었는데 첫 연결이 거절된다면
Compose의 짧은 depends_on은 서비스 준비까지 기다리지 않는다. 같은 지연 서버에서 연결 거절과 HTTP 200을 비교하고, healthcheck를 service_healthy 조건에 연결할 때와 로그에서 준비 시각을 확인하는 방법을 설명한다.
Docker Compose에서 의존 서비스를 먼저 시작하도록 depends_on을 넣었는데도 앱의 첫 연결이 거절될 수 있다. 컨테이너 프로세스가 시작된 것과 서비스가 요청을 받을 준비가 된 것은 다르다. 짧은 depends_on: [server]는 앞의 조건만 기다린다. 준비까지 기다려야 한다면 의존 서비스의 healthcheck와 앱 쪽의 condition: service_healthy를 함께 설정한다. Docker의 시작 순서 설명 (새 탭에서 열림)
다만 연결 거절만 보고 시작 순서 문제라고 단정할 수는 없다. 여기서는 올바른 서비스 이름과 포트를 사용했는데도, 서버가 준비되기 전에 연결한 경우를 다룬다. 주소가 잘못된 문제와 구별하려면 실패 시각을 서버의 준비 시각과 비교해야 한다.
healthcheck가 있어도 앱이 먼저 연결했다
이 차이를 확인하려고 초기화에 6초가 걸리는 합성 HTTP 서버와 한 번만 연결하는 클라이언트를 만들었다. 서버는 기다리는 동안 프로세스만 실행되고, 이후 8000 포트를 열면서 준비 시각을 파일에 남긴다. 클라이언트는 http://server:8000/ready로 요청한다. 서버의 healthcheck는 같은 서버 컨테이너 안에서 http://127.0.0.1:8000/ready에 HTTP 요청을 보낸다.
두 조건 모두 같은 서버 이미지와 healthcheck를 사용했다. 바꾼 것은 클라이언트의 의존 조건뿐이다. 첫 조건은 depends_on: [server], 두 번째는 server의 condition: service_healthy였다. 각각 새 Compose 프로젝트와 내부 네트워크를 사용했고 호스트 포트를 공개하지 않았다. Windows의 Docker Desktop 4.92.0, Engine 29.8.0, Compose v5.5.1에서 실행했으며 컨테이너의 Python은 3.12.14였다.
2026년 10월 21일 실행에서 기록한 순서는 다음과 같다. 시각은 UTC이며 소수점 아래는 밀리초까지만 표시했다.
- 짧은 depends_on: 03:31:13.730 서버 프로세스 시작 → 03:31:14.301 클라이언트 연결 시도 → 03:31:14.371
ConnectionRefusedError: [Errno 111] Connection refused→ 03:31:19.732 서버 준비 기록. 클라이언트는 종료 코드 20으로 끝났다. 20은 이 예제에서 연결 실패에 부여한 값이다. - service_healthy: 03:31:30.801 서버 프로세스 시작 → 03:31:36.803 서버 준비 기록 → 03:31:38.422 첫 성공 healthcheck 종료 → 03:31:42.035 클라이언트 연결 시도 → 03:31:42.132 HTTP 200, 본문
ready. 클라이언트 종료 코드는 0이었다.
두 클라이언트 모두 server 이름을 172.27.0.2로 해석한 뒤 연결했다. 이 IP는 각기 분리된 네트워크에서 이번에 배정된 값이다. 짧은 조건의 클라이언트가 실패한 때에는 준비 파일이 없고 health 상태가 starting인 표본도 남았다. 이후 서버가 healthy가 되어도 이미 실패한 클라이언트는 종료 상태로 남았다. 두 조건의 docker compose up --detach 자체는 모두 종료 코드 0이었다.
따라서 이 실험의 실패는 이름 해석 실패가 아니라 서버가 포트를 열기 약 5.4초 전에 클라이언트가 연결한 결과다. healthcheck를 추가하는 것만으로는 앱이 기다리지 않았다. service_healthy 조건으로 그 검사 결과를 의존 관계에 연결했을 때 첫 요청이 성공했다. 준비부터 요청까지 걸린 시간은 검사 주기와 컨테이너 시작 비용도 포함하므로, 이 시각 차이를 고정 대기 시간으로 복사할 필요는 없다.
준비 검사를 의존 조건에 연결한다
기존 Compose 파일에서 바꿀 부분은 아래와 같다. server와 client는 자신의 서비스 이름으로 바꾸고 기존 image, command 등은 유지한다. 전체 실행 파일이 아니라 준비 검사와 의존 관계에 해당하는 부분이다. 이 검사는 서버 이미지에 Python이 있고, /ready가 준비된 뒤에만 HTTP 200을 반환하는 이번 예제의 조건이다.
services:
server:
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/ready', timeout=1)"]
interval: 1s
timeout: 2s
retries: 10
start_period: 8s
client:
depends_on:
server:
condition: service_healthy
healthcheck.test는 의존 서비스 컨테이너 안에서 실행된다. 그래서 위 검사의 loopback 주소는 서버 자신을 가리킨다. 반면 클라이언트의 연결 주소는 계속 server:8000이다. Python이 없는 이미지에 이 명령을 그대로 넣으면 검사 도구부터 실행되지 않는다. 실제 서비스에서는 이미지 안에 있는 도구로 준비 여부를 확인하는 명령을 정한다. Compose의 healthcheck와 depends_on 설정 (새 탭에서 열림)
위 명령은 HTTP 오류나 연결 실패 때 0이 아닌 종료 코드를 반환했다. 다만 200 응답이 실제로 무엇을 보장하는지는 서비스가 정해야 한다. 초기화 전에도 200을 반환하는 단순 상태 페이지라면 앱이 필요한 준비를 확인하지 못한다. DB 연결이 필요한 앱이라면 그 앱이 요구하는 접속·초기화 조건을 검사할 수 있는지 따로 판단한다. 이번 HTTP 예제로 DB의 준비 조건을 검증한 것은 아니다.
start_period: 8s는 앱을 무조건 8초 동안 재우는 설정이 아니다. 이 기간의 검사 실패는 retries에 세지 않으며, 기간 안이라도 검사가 성공하면 healthy가 된다. interval, timeout, retries는 검사 간격·검사 한 번의 제한·연속 실패 허용 횟수를 정한다. 초기화 시간과 검사 비용에 맞춰 조절한다. Docker의 HEALTHCHECK 동작 (새 탭에서 열림)
현재 healthy라는 표시보다 실패 당시의 순서를 본다
자신의 프로젝트에서는 실패 직후 앱 로그와 준비 검사 기록을 함께 읽는다. 아래는 Docker와 Compose를 사용할 수 있는 PowerShell에서 실행하는 조회 명령이다. 해당 프로젝트의 Compose 파일이 있는 폴더에서 실행하고, 평소 --project-name이나 --file을 지정했다면 조회할 때도 같은 값을 사용한다. server, client는 실제 서비스 이름으로 바꾼다.
docker compose ps --all
docker compose logs --timestamps --tail 30 server client
$serverId = docker compose ps --quiet server
docker inspect --format '{{json .State.Health}}' $serverId
같은 명령을 실험 프로젝트에서 실행했을 때 짧은 조건의 ps --all에는 서버가 (healthy), 클라이언트가 Exited (20)으로 나왔다. 종료한 앱까지 보려면 --all이 필요하다. 로그에는 앞서 적은 연결 거절이 서버의 준비 기록보다 먼저 있었다. 나중의 healthy 표시만 읽었다면 이 순서를 놓쳤을 것이다. Compose ps의 종료 컨테이너 표시 (새 탭에서 열림), 로그의 타임스탬프 옵션 (새 탭에서 열림)
마지막 명령은 health 상태 JSON을 출력한다. 실험에서 수집한 Log에는 첫 검사 실패의 ExitCode: 1과 연결 거절 출력, 이후 성공의 ExitCode: 0이 남았다. Start·End와 앱의 연결 실패 시각을 대조한다. healthcheck가 없거나 기록이 남아 있지 않으면 과거 준비 시점을 이 출력만으로 판단할 수 없다. 서버 ID가 비어 있으면 먼저 조회한 프로젝트와 서비스 이름을 확인한다. docker inspect의 형식 지정 (새 탭에서 열림)
올바른 주소를 사용했고 앱의 실패가 의존 서비스 준비보다 앞섰다면 service_healthy를 적용할 근거가 된다. 이미 첫 성공 검사 뒤에 연결했는데 실패했다면 인증, 실제 요청 경로, 검사 항목과 앱의 요구 조건 차이를 확인한다. 이때는 시작 지연을 더 늘리는 것만으로 원인을 설명할 수 없다.
이번 관찰은 새로 시작하는 두 Linux 컨테이너의 HTTP 첫 요청까지다. 계속 준비되지 않는 서버의 최종 실패 처리, 실제 DB의 인증·스키마 준비, 실행 도중 의존 서비스가 끊겼을 때의 복구는 검증하지 않았다. 시작 시점의 대기 조건과 별개로, 실행 중 연결이 끊길 수 있는 앱에는 제한 시간과 재시도·재연결 정책도 필요하다.