Docker Healthcheck: Configure and Monitor Container Health (2026)
Last updated: March 2026
Docker's HEALTHCHECK instruction lets Docker know how to test whether a container is still working. A container can be running but broken — the web server process is up but returns 500 errors, the database is listening but not accepting connections. Without a health check, Docker and orchestrators like Swarm and Kubernetes treat any running container as healthy. With a health check, you get accurate status reporting, automatic restarts on failure, and safe rolling deployments.
HEALTHCHECK Dockerfile Instruction Syntax
The HEALTHCHECK instruction has two forms:
# Run a command to check health
HEALTHCHECK [OPTIONS] CMD command
# Disable any inherited health check
HEALTHCHECK NONE
A minimal example for an HTTP service:
FROM nginx:alpine
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
The CMD form runs the specified command inside the container. If the command exits with 0, the container is healthy. If it exits with 1, the container is unhealthy. Exit code 2 is reserved (do not use it yourself).
Health Check Options
--interval
How often Docker runs the health check command. Defaults to 30s. Accepts durations like 10s, 1m, 2m30s.
HEALTHCHECK --interval=15s CMD curl -f http://localhost/health || exit 1
Lower intervals catch failures faster but add CPU overhead from frequent checks.
--timeout
How long Docker waits for the health check command to complete. If the command takes longer than this, it is treated as a failure. Defaults to 30s.
HEALTHCHECK --timeout=3s CMD curl -f --max-time 3 http://localhost/ || exit 1
Set --timeout slightly above your expected worst-case response time.
--retries
How many consecutive failures are needed before the container is declared unhealthy. Defaults to 3. This prevents a single transient failure from marking a container unhealthy.
HEALTHCHECK --retries=5 CMD pg_isready -U postgres || exit 1
--start-period
A grace period during which failed health checks do not count toward --retries. This gives the application time to start up before Docker starts counting failures. Defaults to 0s.
HEALTHCHECK --start-period=30s --interval=10s CMD curl -f http://localhost/ || exit 1
Use --start-period for slow-starting applications like Java services or databases doing initialization.
Health States
A container moves through three health states:
| State | Meaning |
|---|---|
starting |
The container has started but the --start-period has not elapsed and no successful check has run yet |
healthy |
The last health check succeeded |
unhealthy |
The last --retries consecutive health checks all failed |
Once unhealthy, Docker will not automatically restart the container unless you have a restart policy configured (--restart=on-failure or --restart=always).
Checking Container Health with docker inspect
To see the current health state of a running container:
docker inspect --format='{{.State.Health.Status}}' my-container
To see the full health check history (last 5 results):
docker inspect --format='{{json .State.Health}}' my-container | python3 -m json.tool
Example output:
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2026-03-26T10:00:00Z",
"End": "2026-03-26T10:00:00.123Z",
"ExitCode": 0,
"Output": ""
}
]
}
You can also see health status in docker ps output:
docker ps --format "table {{.Names}}\t{{.Status}}"
# Output:
# NAMES STATUS
# my-container Up 5 minutes (healthy)
curl vs wget vs Custom Script Checks
Using curl
curl is the most common choice for HTTP health checks:
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
The -f flag makes curl return exit code 22 on HTTP error responses (4xx, 5xx). Without -f, curl succeeds even on a 500 error. Add --max-time to cap the request duration independently of --timeout:
HEALTHCHECK --timeout=5s CMD curl -f --max-time 4 http://localhost:8080/ready || exit 1
Using wget
Alpine-based images often include wget but not curl. The wget equivalent:
HEALTHCHECK CMD wget -qO- http://localhost:8080/health || exit 1
-q silences output, -O- sends the body to stdout. For a simple existence check without reading the body:
HEALTHCHECK CMD wget --spider -q http://localhost:8080/health || exit 1
Custom Script Checks
For complex checks — database connectivity, queue depth, disk space — write a shell script:
COPY healthcheck.sh /usr/local/bin/healthcheck.sh
RUN chmod +x /usr/local/bin/healthcheck.sh
HEALTHCHECK --interval=30s --timeout=10s CMD healthcheck.sh
#!/bin/sh
# healthcheck.sh
# Check HTTP endpoint
curl -f http://localhost:8080/ready || exit 1
# Check database connection
pg_isready -h db -U myuser -d mydb || exit 1
exit 0
Database-Specific Checks
PostgreSQL:
HEALTHCHECK --interval=10s --timeout=5s CMD pg_isready -U postgres || exit 1
MySQL / MariaDB:
HEALTHCHECK --interval=10s --timeout=5s \
CMD mysqladmin ping -h localhost --silent || exit 1
Redis:
HEALTHCHECK --interval=10s --timeout=3s \
CMD redis-cli ping | grep -q PONG || exit 1
Docker Compose Healthcheck Syntax
In a compose.yml file, health checks are defined under each service:
services:
web:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Use CMD when you pass arguments as an array. Use CMD-SHELL when you need shell features like pipes or ||.
To disable an inherited health check in Compose:
healthcheck:
disable: true
Using depends_on with Health Checks
Combine depends_on with condition: service_healthy to delay a service until its dependency is healthy:
services:
web:
image: myapp:latest
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
start_period: 20s
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
This is more reliable than depends_on without a condition, which only waits for the container to start, not for the service to be ready. For a full multi-container Compose setup, see the Docker Compose Getting Started guide.
Health Check Best Practices
Use a dedicated health endpoint. For web services, implement a /health or /ready route that checks internal dependencies and returns a 2xx status when ready. Do not use the home page — it may return 200 even when a database connection is broken.
Keep health checks fast. A health check that takes 3 seconds to respond adds up when Docker checks every 10 seconds across dozens of containers.
Separate liveness from readiness. In Kubernetes, use separate probes. In Docker standalone, a single check is usually sufficient, but consider whether your check verifies "alive" (process running) or "ready" (able to serve traffic).
Watch your base image. Many minimal images (scratch, distroless) do not include curl, wget, or a shell. Use CMD-SHELL only when a shell is available. Consider copying a small static binary into your image if needed.
Test the health check locally. Run the check command manually inside the container to confirm it behaves as expected:
docker exec my-container curl -f http://localhost:8080/health
echo "Exit code: $?"
Troubleshooting Health Checks
If a container is stuck in unhealthy state:
# View the last health check output
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' my-container
# Run the health check command manually
docker exec my-container sh -c "curl -f http://localhost:8080/health"
# Watch health events
docker events --filter event=health_status
Common causes of unexpected failures:
- The application has not bound to
0.0.0.0(only127.0.0.1) — check from inside the container, not the host. curlorwgetis not installed in the image.- The
--timeoutis shorter than the application's response time under load. - The
--start-periodis too short for a slow-starting service.