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_periodcho 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
curlhoặcwget)
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ó ExitCode và Output — đó 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ả db và redis đề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ừ starting → healthy 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 chung —
pg_isreadyvàredis-cli pingcho 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_healthytrongdepends_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.

