Healthchecks and restart policies

Docker · Advanced · 10 min

A running process isn't necessarily a working service; health and restart settings close that gap.

Docker only knows whether the main process is running. A web server stuck in a deadlock is still "Up". A healthcheck gives Docker a command to test real health:

HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
  CMD curl -fsS http://localhost:8000/healthz || exit 1

Docker runs the command inside the container on that schedule. Exit code 0 means healthy, 1 unhealthy. The status moves from starting to healthy, or to unhealthy after retries consecutive failures, and shows in docker ps and docker inspect. --start-period gives slow starters time before failures count. The check needs its tool (curl, wget or a tiny binary) in the image, which matters with distroless images. You can also set or override it at run time (--health-cmd) or in Compose (healthcheck:).

What uses health status: Compose's depends_on with condition: service_healthy waits for it, Swarm replaces unhealthy tasks, and monitoring can alert on it. Plain docker run does not restart an unhealthy container by itself. (Kubernetes ignores Docker healthchecks and uses its own probes.)

Restart policies (--restart) handle crashes and reboots:

  • no (default): never restart.
  • on-failure[:5]: restart only on a non-zero exit, optionally at most 5 times.
  • always: restart whenever it stops; a container you stopped manually comes back when the daemon restarts.
  • unless-stopped: like always, but a manual docker stop is respected across daemon restarts.

Docker waits progressively longer between restarts of a crash-looping container. For servers, unless-stopped with a healthcheck is a sensible single-host baseline.

Example

# compose.yaml
services:
  db:
    image: postgres:17
    environment: { POSTGRES_PASSWORD: dev }
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s
  api:
    image: acme/api:1.4.2
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
# docker compose ps  ->  db  Up 20 seconds (healthy)

Remember

  • HEALTHCHECK runs a command in the container: 0 healthy, 1 unhealthy
  • Plain Docker reports unhealthy but doesn't restart it; Compose and Swarm can act on it
  • Restart policies: no, on-failure, always, unless-stopped
  • depends_on with condition: service_healthy waits for readiness

Watch out for

  • Using a healthcheck command (like curl) that isn't installed in the image, so the container is always unhealthy.

Check yourself

  1. A container's healthcheck reports unhealthy and it runs with --restart always. What does Docker do?
  2. Which restart policy keeps a container you stopped manually from coming back after a daemon restart?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/reference/dockerfile/#healthcheck