--- title: "Docker Healthchecks" domain: selfhosting category: docker tags: [docker, healthcheck, monitoring, uptime-kuma, compose] status: published created: 2026-03-23 updated: 2026-03-23 --- # Docker Healthchecks A Docker healthcheck tells the daemon (and any monitoring tool) whether a container is actually working — not just running. Without one, a container shows as `Up` even if the app inside is crashed, deadlocked, or waiting on a dependency. ## Why It Matters Tools like Uptime Kuma report containers without healthchecks as: > Container has not reported health and is currently running. As it is running, it is considered UP. Consider adding a health check for better service visibility. A healthcheck upgrades that to a real `(healthy)` or `(unhealthy)` status, making monitoring meaningful. ## Basic Syntax (docker-compose) ```yaml healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s ``` | Field | Description | |---|---| | `test` | Command to run. Exit 0 = healthy, non-zero = unhealthy. | | `interval` | How often to run the check. | | `timeout` | How long to wait before marking as failed. | | `retries` | Failures before marking `unhealthy`. | | `start_period` | Grace period on startup before failures count. | ## Common Patterns ### HTTP service (wget — available in Alpine) ```yaml healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:2368/"] interval: 30s timeout: 10s retries: 3 start_period: 30s ``` ### HTTP service (curl) ```yaml healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s ``` ### MySQL / MariaDB ```yaml healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret"] interval: 10s timeout: 5s retries: 3 start_period: 20s ``` ### PostgreSQL ```yaml healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 ``` ### Redis ```yaml healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3 ``` ### TCP port check (no curl/wget available) ```yaml healthcheck: test: ["CMD-SHELL", "nc -z localhost 8080 || exit 1"] interval: 30s timeout: 5s retries: 3 ``` ## Using Healthchecks with `depends_on` Healthchecks enable proper startup ordering. Instead of a fixed sleep, a dependent container waits until its dependency is actually ready: ```yaml services: app: depends_on: db: condition: service_healthy db: image: mysql:8.0 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 3 start_period: 20s ``` This prevents the classic race condition where the app starts before the database is ready to accept connections. ## Checking Health Status ```bash # See health status in container list docker ps # Get detailed health info including last check output docker inspect --format='{{json .State.Health}}' | jq ``` ## Ghost Example Ghost (Alpine-based) uses `wget` rather than `curl`: ```yaml healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:2368/ghost/api/v4/admin/site/"] interval: 30s timeout: 10s retries: 3 start_period: 30s ``` ## Gotchas & Notes - **Alpine images** don't have `curl` by default — use `wget` or install curl in the image. - **`start_period`** is critical for slow-starting apps (databases, JVM services). Failures during this window don't count toward `retries`. - **`CMD` vs `CMD-SHELL`** — use `CMD` for direct exec (no shell needed), `CMD-SHELL` when you need pipes, `&&`, or shell builtins. - **Uptime Kuma** will pick up Docker healthcheck status automatically when monitoring via the Docker socket — no extra config needed. ## See Also - [[debugging-broken-docker-containers]] - [[netdata-docker-health-alarm-tuning]]