Sửa lỗi 'service is unhealthy' trong Docker Compose

intermediate🐳 Docker2026-03-17| Docker Desktop / Docker Engine 20.x+, Docker Compose v2.x, Linux/macOS/Windows

Error Message

service "myservice" is unhealthy
#docker#docker-compose#healthcheck#container

Lỗi Gặp Phải

Bạn chạy docker compose up và mọi thứ đột ngột dừng lại:

Error response from daemon: service "myservice" is unhealthy

Hoặc một container phụ thuộc từ chối khởi động vì service upstream của nó không bao giờ chuyển sang trạng thái healthy. Dù thế nào, bạn cũng bị kẹt.

Nguyên nhân: Docker đã chạy lệnh healthcheck bên trong container của bạn và lệnh đó liên tục trả về exit code khác 0. Sau khi đạt đến giới hạn retry, Docker đánh dấu container là unhealthy. Tùy vào cấu hình Compose, nó sẽ tắt container ngay lập tức hoặc chặn tất cả những gì depends_on nó.

Nguyên Nhân Gốc Rễ

  • Lệnh healthcheck sai — sai đường dẫn, sai công cụ, sai port
  • Service khởi động lâu hơn thời gian start_period cho phép
  • Service thực sự bị lỗi bên trong container (cấu hình sai, crash loop)
  • Một dependency mà healthcheck kiểm tra — DB hoặc API ngoài — chưa sẵn sàng
  • Công cụ healthcheck không được cài trong image (ví dụ: thiếu curl hoặc wget)

Bước 1: Xem Docker Đang Thấy Gì

Đừng đoán mò. Lấy output healthcheck thô trước:

# Hiển thị trạng thái health và output lần kiểm tra cuối
docker inspect --format='{{json .State.Health}}' myservice | jq .

Mảng Log là phần quan trọng nhất. Mỗi entry có ExitCodeOutput — đó là stdout/stderr thực tế từ lệnh healthcheck của bạn. Chín trên mười lần, thông tin này cho bạn biết chính xác vấn đề ở đâu.

# Kiểm tra xem container có đang chạy không
docker compose ps
docker compose logs myservice

Cách Sửa 1: Sửa Lại Lệnh Healthcheck

Nguyên nhân phổ biến nhất: công cụ bạn đang gọi không có trong image. Hoặc endpoint sai. Đây là healthcheck hoạt động tốt cho một Node.js API trên port 3000:

services:
  api:
    image: my-node-app
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

Không chắc công cụ nào có trong image? Kiểm tra trực tiếp:

docker exec myservice wget -qO- http://localhost:3000/health
docker exec myservice curl -f http://localhost:3000/health

Với PostgreSQL, dùng pg_isready có sẵn — không cần cài thêm gì:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 3s
  retries: 5

Redis còn đơn giản hơn:

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 5s
  timeout: 3s
  retries: 5

Cách Sửa 2: Cho Service Chậm Thêm Thời Gian Với start_period

Ứng dụng Java và Spring Boot nổi tiếng với vấn đề này. Một service Spring Boot có thể mất 30–90 giây để khởi động hoàn toàn. Nếu không có start_period đủ lớn, Docker sẽ đánh dấu nó unhealthy trước khi nó kịp load xong.

Điều quan trọng cần nhớ: các lần kiểm tra thất bại trong start_period không tính vào retries. Đây là khoảng thời gian ân hạn, không phải đếm ngược tử thần.

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
  interval: 15s
  timeout: 10s
  retries: 5
  start_period: 60s   # Cho 60 giây trước khi bắt đầu đếm retry

Xem log container để biết thời gian khởi động thực tế, rồi đặt start_period bằng giá trị đó cộng thêm 10–15 giây đệm.

Cách Sửa 3: Dùng condition: service_healthy trong depends_on

Mặc định, depends_on chỉ chờ container khởi động — không phải chờ đến khi sẵn sàng. Một container database có thể ở trạng thái "started" được 5 giây trước khi Postgres thực sự chấp nhận kết nối. App của bạn kết nối quá sớm và bị lỗi.

Cách sửa chỉ cần thêm một dòng cho mỗi dependency:

services:
  app:
    image: my-app
    depends_on:
      db:
        condition: service_healthy   # Chờ đến khi db vượt qua health check
      redis:
        condition: service_healthy

  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

  redis:
    image: redis:7
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

Cách này đảm bảo app sẽ không khởi động cho đến khi cả dbredis đều được xác nhận healthy.

Cách Sửa 4: Tạm Thời Tắt Healthcheck

Hữu ích khi bạn cần xác định vấn đề. Cấu hình healthcheck bị sai, hay bản thân service đang crash? Tắt healthcheck đi sẽ trả lời nhanh câu hỏi đó:

services:
  myservice:
    image: my-image
    healthcheck:
      disable: true

Nếu container chạy tốt khi tắt healthcheck nhưng lại lỗi khi bật, thì cấu hình healthcheck chính là vấn đề. Đừng để cài đặt này trên môi trường production.

Cách Sửa 5: Bản Thân Service Bị Lỗi

Đôi khi healthcheck hoàn toàn đúng. Container thực sự đang bị lỗi. Hãy đào sâu vào log:

docker compose logs --tail=100 myservice

# Khởi động và theo dõi lỗi theo thời gian thực
docker compose up 2>&1 | grep -E "(error|Error|fatal|Fatal|unhealthy)"

Các nghi phạm thường gặp: thiếu biến môi trường, sai thông tin đăng nhập database, xung đột port trên host, hoặc volume mount với quyền mà Docker không thể ghi vào.

Kiểm Tra Kết Quả

Sau khi áp dụng cách sửa, theo dõi trạng thái container theo thời gian thực:

watch -n2 'docker compose ps'

Cột STATUS phải chuyển từ startinghealthy trong khoảng thời gian start_period + (interval × retries) đã cấu hình. Kiểm tra lại với inspect:

docker inspect --format='{{.State.Health.Status}}' myservice
# Kết quả mong đợi: healthy

Phòng Ngừa

  • Kiểm tra lệnh bằng docker exec trước — chạy lệnh healthcheck thủ công bên trong container trước khi viết vào compose.yml. Tiết kiệm rất nhiều thời gian thử và sai.
  • Luôn đặt start_period tường minh — giá trị mặc định là 0s, nghĩa là các lần kiểm tra bắt đầu ngay khi container khởi động. Hầu hết mọi service không tầm thường đều cần ít nhất 10–30s ở đây.
  • Ưu tiên kiểm tra cụ thể hơn TCP probe chung chungpg_isreadyredis-cli ping cho output có ý nghĩa khi lỗi; TCP check thô chỉ cho bạn biết port đang mở, không phải service đang hoạt động.
  • Thêm healthcheck cho mọi dependency có trạng thái (database, cache, message queue) và kết hợp với condition: service_healthy trong depends_on.
  • Ghi lại output healthcheck trong log CI — các vấn đề timing không ổn định dễ phát hiện trong pipeline hơn nhiều so với sự cố production lúc 2 giờ sáng.

Related Error Notes