Health Check Probes
Implement container health check probes in Docker. This lesson covers why probes matter, how to define HEALTHCHECK and readiness endpoints, and hands-on steps to configure them. Includes troubleshooting, edge cases, and what to learn next.
Focus: implement container health check probes
Containers are ephemeral by design — they start, run, and can crash without warning. Without health check probes, your orchestration tools (or even your own Docker setup) have no way of knowing if a container is truly ready to serve traffic or has silently failed. This blind spot can turn a minor glitch into a cascading outage. In this lesson, you'll learn how to implement container health check probes so your containers self-report their status, enabling automated recovery and a more resilient system.
The problem this lesson solves
Imagine you have a web application running in a container. The container process starts, but your database connection pool fails to initialize. From Docker's perspective, the container is "up" — the process is alive. But from your application's perspective, the container is useless. Every request returns a 500 error. This is the zombie container problem: a process that's technically running but not serving its intended function.
Health check probes solve this by letting your container define a custom command or HTTP endpoint that Docker (or an orchestrator like Kubernetes) can poll periodically. If the probe fails, the container is considered unhealthy and can be restarted or removed from the load balancer pool.
Without probes: - Load balancers may route traffic to dead containers. - Orchestrators (e.g., Docker Swarm, Kubernetes) cannot self-heal. - Debugging becomes reactive instead of proactive.
The cost of ignoring health checks is downtime, degraded user experience, and manual intervention. The solution is a simple, declarative health check configuration embedded in your Dockerfile or Compose file.
Core concept / mental model
Think of a health check probe as a heartbeat monitor for your container. Just as a doctor checks your pulse to see if you're alive, Docker checks a probe to see if your container is functioning.
There are two main types of health check probes in Docker:
HEALTHCHECK(Dockerfile instruction): Defines a command that Docker runs inside the container at regular intervals. The exit code determines health:0means healthy,1means unhealthy.- Readiness probe (via Docker Compose or Swarm services): A similar concept, often HTTP-based, that checks if a container is ready to accept traffic.
Docker's HEALTHCHECK is the most common and flexible. It supports:
- --interval (default 30s): How often to run the check.
- --timeout (default 30s): How long to wait for a response before marking as failed.
- --start-period (default 0s): Grace period for slow-starting containers (e.g., those loading models or establishing DB connections).
- --retries (default 3): Consecutive failures before the container is marked unhealthy.
Pro tip: The start-period is critical for containers that take time to initialize. Without it, your container might be killed before it's ready.
How it works step by step
- Define the health check command – This can be a shell command, a script, or an HTTP request using tools like
curlorwget. The command must exit with0for healthy,1for unhealthy. - Add
HEALTHCHECKto your Dockerfile – Use theHEALTHCHECKinstruction with your chosen parameters. - Build and run the container – Docker automatically executes the health check according to the interval you set.
- Inspect container health – Use
docker ps(which shows health status in theSTATUScolumn) ordocker inspectto see detailed health logs. - Define readiness probes for orchestration – In Docker Compose or Swarm, you can add
healthcheckunder the service definition, which works identically to the Dockerfile version.
The health check runs inside the container's namespace, so the command must be available within the container image. If your check requires curl, you need to include it in your base image or rely on built-in shell tools.
HTTP-based health checks
For web services, the most common health check is an HTTP endpoint (e.g., /health). You expose this endpoint in your application code and then use curl to probe it.
Example endpoint in Python Flask:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/health')
def health():
# Check database connection, cache, etc.
return jsonify({"status": "healthy"}), 200
Then in your Dockerfile:
HEALTHCHECK --interval=10s --timeout=5s --start-period=20s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
Hands-on walkthrough
Let's build a container with a custom health check.
Example 1: Basic shell command health check
Create a Dockerfile with a simple health check that checks if a process is running:
FROM alpine:latest
# Simulate a long-running application
CMD sleep 3600
# Health check: verify the 'sleep' process is alive
HEALTHCHECK --interval=5s --timeout=3s --start-period=2s --retries=2 \
CMD pgrep sleep || exit 1
Build and run:
docker build -t health-check-demo .
docker run -d --name demo health-check-demo
Check health status:
docker ps --format "table {{.Names}}\t{{.Status}}"
Expected output:
NAMES STATUS
demo Up 10 seconds (healthy)
Example 2: HTTP-based health check with Node.js
Create a simple Express app:
// app.js
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000);
Dockerfile with health check:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
Example 3: Docker Compose with health check dependencies
version: '3.8'
services:
backend:
build: .
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
proxy:
image: nginx:alpine
depends_on:
backend:
condition: service_healthy
ports:
- "80:80"
This ensures the proxy service waits until backend is healthy before starting.
Compare options / when to choose what
| Health check type | When to use | Drawback |
|---|---|---|
Shell command (CMD) |
Simple process checks, no HTTP | Requires the tool inside the container; can be slow |
HTTP endpoint (curl/wget) |
Web services, APIs | Adds latency and requires the tool; takes time up to --timeout |
Docker Compose healthcheck |
Swarm or multi-service stacks | Not available for standalone docker run without extra tooling |
| Kubernetes liveness/readiness probes | Orchestrated environments | External to Docker; requires K8s setup |
- For standalone containers (local dev), use HEALTHCHECK in the Dockerfile with a shell or HTTP command. |
||
- For Docker Compose (multi-service apps), prefer the Compose-level healthcheck block for visibility and dependency control. |
||
| - For production orchestrators (K8s, Swarm), use the orchestrator's own probes for more granular control (e.g., separate liveness vs readiness vs startup probes). |
Troubleshooting & edge cases
Common mistake 1: Forgetting to install health check tools
If your image doesn't include curl or wget, the health check will fail with "command not found."
HEALTHCHECK CMD curl -f http://localhost:80/ || exit 1
# Fails with: curl: not found
Fix: Use Alpine or add the tool: RUN apk add --no-cache curl
Common mistake 2: Too aggressive interval
Setting --interval=1s on a heavy application will degrade performance. Respect your app's latency.
Common mistake 3: Ignoring start-period
A container with a 60-second startup time will fail its health check repeatedly without --start-period=60s.
Edge case: Health check exit codes
Docker treats non-zero exit codes as unhealthy. If your command returns 2 (e.g., permission error), it's still unhealthy. Only 0 is healthy.
Edge case: Network-bound services
Your health check command runs inside the container network. If your app listens on 0.0.0.0, use 127.0.0.1 in the check – still localhost.
Edge case: Logging health checks
Health checks don't appear in docker logs. Use docker inspect <container_id> --format '{{json .State.Health}}' to see the last 5 check results.
What you learned & what's next
You now know how to implement container health check probes in Docker. You can:
- Explain the zombie container problem and why probes matter.
- Add a HEALTHCHECK instruction to a Dockerfile with appropriate interval, timeout, start period, and retries.
- Write a health check command using shell commands or HTTP tools.
- Configure health checks in Docker Compose with service dependencies.
- Troubleshoot common failures like missing tools or insufficient start period.
In the next lesson, you'll learn how to use Docker Compose for multi-service applications – where health checks become even more critical for coordinating startup order and self-healing.
Next step: Practice by adding a health check to an existing containerized application. Experiment with different intervals and observe how
docker psreflects health status changes.
Practice recap
Add a /health endpoint to a small web app (Python Flask or Node.js Express) that returns 200 when a critical dependency (like a file or environment variable) is ready. Write a Dockerfile with an appropriate HEALTHCHECK that uses curl or wget to probe this endpoint. Run the container and observe how docker ps shows the health status. Experiment with different --start-period values to see how they affect startup behavior.
Common mistakes
- Forgetting to install health check tools (curl, wget, pgrep) inside the container image, causing 'command not found' errors and marking the container unhealthy.
- Setting too aggressive an interval (e.g., 1s) on a heavy application, degrading performance and causing false positives.
- Omitting the
--start-periodfor slow-starting containers (e.g., those loading models or connecting to databases), causing them to be marked unhealthy before they fully initialize. - Using non-zero exit codes beyond 0 and 1, which are still treated as unhealthy by Docker (only exit code 0 is healthy).
Variations
- Use
wgetinstead ofcurlin alpine-based images wherewgetis more lightweight and available by default. - Define health checks in Docker Compose
healthcheck:block instead of the Dockerfile for per-service control and dependency chaining. - Implement a custom health check script that performs multi-step validation (e.g., check DB, cache, and external API) instead of a single endpoint.
Real-world use cases
- A Node.js API container uses an HTTP /health endpoint that checks database connectivity and returns 200/503, with Docker's HEALTHCHECK restarting it on failure.
- A machine learning inference container runs a Python script that verifies model file integrity and GPU availability before reporting healthy.
- An e-commerce stack with Docker Compose uses health check dependencies to ensure the database is fully initialized before the web server starts accepting traffic.
Key takeaways
- Health check probes solve the zombie container problem by verifying actual functionality, not just process existence.
- Use
HEALTHCHECKin Dockerfile with--interval,--timeout,--start-period, and--retriesflags for precise control. - The health check command must exist inside the container — install tools like curl or wget if needed.
- Set a generous
--start-periodto allow slow-starting containers to initialize before health checks begin. - In Docker Compose, use
healthcheck:under services anddepends_on:withcondition: service_healthyfor ordered startup. - Monitor health status with
docker psordocker inspectto debug health check failures.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.