Docker Compose에서 PHP·Nginx·MariaDB 시작 순서 문제 해결 — healthcheck와 service_healthy

조회수 11

도입

depends_on만 적으면 DB가 준비될 때까지 PHP가 기다릴 것이라고 오해하기 쉽다. Compose가 보장하는 기본 순서는 컨테이너 프로세스의 시작 순서다. MariaDB가 접속과 쿼리를 받을 준비가 됐다는 뜻은 아니다.

실패를 만드는 최소 구성

services:
  db:
    image: mariadb:10.11
    environment:
      MARIADB_DATABASE: app
      MARIADB_USER: app
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

  php:
    build: ./php
    depends_on:
      - db
    environment:
      DB_HOST: db
      DB_NAME: app
      DB_USER: app
      DB_PASSWORD: ${DB_PASSWORD}

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php

volumes:
  db_data:

.env는 커밋하지 않는다.

DB_PASSWORD=replace-with-a-random-value
DB_ROOT_PASSWORD=replace-with-another-random-value

docker compose up --build 직후 PHP가 한 번만 DB에 연결하도록 만들면 초기화가 느린 환경에서 Connection refused 또는 연결 실패가 발생할 수 있다.

readiness를 healthcheck로 표현하기

DB 서비스에 실제 인증과 간단한 쿼리를 수행하는 검사를 추가한다.

services:
  db:
    image: mariadb:10.11
    environment:
      MARIADB_DATABASE: app
      MARIADB_USER: app
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    healthcheck:
      test:
        - CMD-SHELL
        - >
          mariadb-admin ping
          -h 127.0.0.1
          -u"$${MARIADB_USER}"
          -p"$${MARIADB_PASSWORD}"
          --silent
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 20s
    volumes:
      - db_data:/var/lib/mysql

  php:
    build: ./php
    depends_on:
      db:
        condition: service_healthy

Compose 파일의 ${...}는 호스트에서 치환하지 않고 컨테이너 내부 환경변수로 전달하기 위한 표기다. start_period 동안의 실패는 초기화 시간으로 취급하고, 이후 연속 실패가 임계값을 넘으면 unhealthy가 된다.

mariadb-admin ping은 서버 응답을 확인한다. 애플리케이션 계정의 특정 DB 접근까지 보장해야 한다면 다음처럼 실제 쿼리로 강화할 수 있다.

test:
  - CMD-SHELL
  - >
    mariadb -h 127.0.0.1
    -u"$${MARIADB_USER}"
    -p"$${MARIADB_PASSWORD}"
    "$${MARIADB_DATABASE}"
    -e "SELECT 1" >/dev/null

구성과 상태 검증

docker compose config
docker compose up --build
docker compose ps
docker compose logs --timestamps db php nginx
docker compose exec db mariadb -uapp -p app -e "SELECT 1"

config 출력에는 secret 값이 포함될 수 있으므로 공개 CI 로그에 그대로 남기지 않는다. ps에서 DB가 healthy가 된 뒤 PHP가 시작되는지 타임스탬프로 확인한다.

재시작과 healthcheck의 경계

service_healthy는 최초 생성 순서의 경합을 줄인다. 실행 중 DB가 내려갔다고 PHP 애플리케이션의 모든 연결이 자동 복구되는 것은 아니다. 애플리케이션에도 제한된 재시도, 연결 재생성, 실패 응답이 필요하다.

또한 컨테이너 런타임의 자동 재시작과 Compose 의존성의 restart: true는 같은 개념이 아니다. 명시적인 Compose 작업 뒤 의존 서비스를 함께 재시작할 필요가 있는지 별도로 시험한다.

named volume 데이터 유지 확인

docker compose exec db mariadb -uapp -p app \
  -e "CREATE TABLE IF NOT EXISTS marker(id INT PRIMARY KEY); INSERT IGNORE INTO marker VALUES(1);"

docker compose down
docker compose up -d
docker compose exec db mariadb -uapp -p app -e "SELECT * FROM marker"

docker compose down은 기본적으로 named volume을 지우지 않는다. 반면 down -v는 볼륨을 제거하므로 운영 데이터가 있는 환경에서 실행하면 안 된다.

점검 체크리스트

  • 단순 시작이 아니라 실제 준비 상태를 검사하는가?
  • healthcheck가 애플리케이션과 같은 인증 경로를 사용하는가?
  • start_period, timeout, retries가 초기화 시간에 맞는가?
  • 애플리케이션에도 제한된 연결 재시도가 있는가?
  • 비밀번호가 이미지, Compose 파일, 로그에 고정되지 않았는가?
  • 볼륨 삭제 명령의 영향을 알고 있는가?

함께 읽기

참고 자료

확인일: 2026-07-29

한 줄 요약

depends_on만으로는 DB 준비가 보장되지 않으며, readiness healthcheck와 service_healthy, 애플리케이션 재시도를 함께 설계해야 한다.

댓글 0

  • 첫 번째 댓글을 남겨보세요.