}

Docker Healthcheck: Configure and Monitor Container Health (2026)

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 (only 127.0.0.1) — check from inside the container, not the host.
  • curl or wget is not installed in the image.
  • The --timeout is shorter than the application's response time under load.
  • The --start-period is too short for a slow-starting service.

Related Articles