Skip to main content

Docker Compose Health Check Patterns - Portable Best Practices

Overview

This guide documents portable health check patterns for Docker Compose services. Following these patterns ensures health checks work across different base images, container runtimes, and environments without relying on utilities that may not be present.

Table of Contents


Why Portable Health Checks Matter

The Problem

Many Docker images use minimal base images (Alpine, distroless, scratch) that:
  • ❌ Lack common utilities (curl, wget, nc, telnet)
  • ❌ Don’t include health check tools (grpc_health_probe, httpie)
  • ❌ May not have shell interpreters (/bin/bash, /bin/sh)
  • ❌ Minimize attack surface by removing non-essential binaries

The Solution

Use service-native health check commands that are guaranteed to exist in the container:
  • ✅ Database clients (pg_isready, redis-cli, mongosh)
  • ✅ Service-specific health endpoints (kc.sh show-config)
  • ✅ Built-in health check binaries shipped with the service
  • ✅ TCP port checks as fallback (most portable)

Benefits

  1. Reliability: Health checks don’t fail due to missing utilities
  2. Portability: Works across different image variants (alpine, distroless, etc.)
  3. Security: Doesn’t require installing additional packages
  4. Performance: Uses lightweight native commands
  5. Maintainability: Survives base image updates

Common Pitfalls

❌ Anti-Pattern 1: Assuming curl Exists

Why it fails:
  • Alpine-based images: curl not installed by default
  • Distroless images: No package manager to install utilities
  • Security-hardened images: Utilities removed to reduce attack surface
Example failures:
  • qdrant:v1.15.1 - lacks curl, wget, grpc_health_probe
  • quay.io/keycloak/keycloak:latest - minimal image without curl

❌ Anti-Pattern 2: Using wget Without Verification

❌ Anti-Pattern 3: Hardcoded Shell Paths

❌ Anti-Pattern 4: External Dependencies


Portable Patterns by Service Type

PostgreSQL

✅ Recommended: Use pg_isready
Why it works:
  • pg_isready is always included in official PostgreSQL images
  • Checks database readiness, not just process existence
  • Returns proper exit codes (0 = healthy, 1/2 = unhealthy)
Alternative (if pg_isready unavailable):

Redis

✅ Recommended: Use redis-cli ping
Why it works:
  • redis-cli always bundled with Redis
  • ping command is lightweight and fast
  • Returns PONG on success (exit code 0)
For Redis with AUTH:

Keycloak

✅ Recommended: Use built-in kc.sh command
Why it works:
  • kc.sh is the native Keycloak management script
  • show-config verifies configuration loaded
  • grep -q 'kc.db' confirms database configured
  • No external utilities required
Alternative (if HTTP endpoint available):

MongoDB

✅ Recommended: Use mongosh or mongo client
For older MongoDB versions (<5.0):

OpenFGA (gRPC Services)

✅ Recommended Option 1: Use bundled grpc_health_probe
Requirements:
  • Verify grpc_health_probe is in the image
  • Check with: docker run --rm <image> ls /usr/local/bin/grpc_health_probe
✅ Recommended Option 2: TCP port check (most portable)
Why it works:
  • No external utilities needed (uses Bash built-in /dev/tcp)
  • Works on any image with Bash
  • Checks if port is listening
For Alpine/sh-only environments:

Qdrant (Vector Database)

✅ Recommended: TCP port check
Why TCP check:
  • Qdrant minimal image has no HTTP clients
  • Installing utilities defeats minimal image purpose
  • Port listening = service ready for most use cases
Alternative (if Python available):

Elasticsearch

✅ Recommended: Use curl if available, fallback to TCP
For minimal images:

RabbitMQ

✅ Recommended: Use rabbitmqctl
Alternative (check cluster health):

Health Check Configuration Guidelines

Timing Parameters Best Practices

Rules:
  1. interval >= timeout (required by Docker Compose validation)
  2. start_period should cover typical startup time
  3. retries should account for temporary failures (network blips)
Service-Specific Recommendations:

Exit Codes

Health check commands must return proper exit codes:
  • 0: Healthy (container ready)
  • 1: Unhealthy (container not ready or failed)
Example with explicit exit codes:

Shell vs. Exec Form

CMD-SHELL Form (requires shell):
  • Runs command through /bin/sh -c
  • Required for: pipes, redirections, variable expansion
  • Risk: Fails if /bin/sh doesn’t exist (distroless images)
CMD Form (direct exec):
  • Executes command directly (no shell)
  • Preferred when possible (more portable)
  • Works in distroless/minimal images

Testing Health Checks

Verify Health Check Works

1. Start service and monitor health status:
2. Check health check logs:
3. Manually run health check command:

Test in Different Image Variants

Validate Timing Parameters

Run validation:

Troubleshooting

Health Check Never Becomes Healthy

Symptom: Container stays in starting or unhealthy state Debug steps:
  1. Check if command exists:
  2. Run health check manually:
  3. Check service is actually running:
  4. Verify ports are listening:
  5. Check start_period is sufficient:

Health Check Command Not Found

Symptom: executable file not found or command not found Solutions:
  1. Verify command path:
  2. Check shell availability:
  3. Use absolute paths:

Health Check Times Out

Symptom: Health checks fail with timeout Solutions:
  1. Increase timeout:
  2. Use faster health check:
  3. Check network latency:

Permission Denied Errors

Symptom: Health check fails with permission errors Solutions:
  1. Run as correct user:
  2. Check file permissions:
  3. Use sudo if available:

Decision Tree: Choosing the Right Health Check


Complete Examples

Example 1: PostgreSQL with Optimal Settings

Example 2: Redis with Auth

Example 3: Keycloak with Slow Startup

Example 4: Multi-Service with Dependencies


References


Summary: Quick Reference

Golden Rules:
  1. Use service-native commands when possible
  2. Fallback to TCP checks for minimal images
  3. interval >= timeout (required)
  4. Test in actual container before deploying
  5. Don’t assume curl/wget exist
  6. Don’t use hardcoded shell paths

Last Updated: 2024-11-17 Maintained By: Infrastructure Team Related: docker-compose.test.yml, docker-compose.dev.yml