Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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: 0 means healthy, 1 means 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

  1. Define the health check command – This can be a shell command, a script, or an HTTP request using tools like curl or wget. The command must exit with 0 for healthy, 1 for unhealthy.
  2. Add HEALTHCHECK to your Dockerfile – Use the HEALTHCHECK instruction with your chosen parameters.
  3. Build and run the container – Docker automatically executes the health check according to the interval you set.
  4. Inspect container health – Use docker ps (which shows health status in the STATUS column) or docker inspect to see detailed health logs.
  5. Define readiness probes for orchestration – In Docker Compose or Swarm, you can add healthcheck under 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 ps reflects 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-period for 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

  1. Use wget instead of curl in alpine-based images where wget is more lightweight and available by default.
  2. Define health checks in Docker Compose healthcheck: block instead of the Dockerfile for per-service control and dependency chaining.
  3. 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 HEALTHCHECK in Dockerfile with --interval, --timeout, --start-period, and --retries flags for precise control.
  • The health check command must exist inside the container — install tools like curl or wget if needed.
  • Set a generous --start-period to allow slow-starting containers to initialize before health checks begin.
  • In Docker Compose, use healthcheck: under services and depends_on: with condition: service_healthy for ordered startup.
  • Monitor health status with docker ps or docker inspect to debug health check failures.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.