tl  tr
  Home | Tutorials | Articles | Videos | Products | Tools | Search
Interviews | Open Source | Tag Cloud | Follow Us | Bookmark | Contact   
 Container Management > Docker > Docker Health Checks and Container Best Practices

Docker Health Checks and Container Best Practices

Author: Venkata Sudhakar

Running containers in production requires more than just a basic Dockerfile. Production containers need health checks so orchestrators like Docker Swarm and Kubernetes know when a container is ready to serve traffic and when it has become unhealthy. They need resource limits to prevent a single container from consuming all host memory and starving other containers. They need to run as non-root users to limit the blast radius of security vulnerabilities. And they need to be built with minimal image sizes to reduce attack surface and improve pull times.

Docker HEALTHCHECK instruction defines a command that Docker runs periodically inside the container to determine its health status. If the health check fails consecutively for more than the configured retries, Docker marks the container as unhealthy. In Docker Swarm, unhealthy containers are automatically replaced. In Kubernetes, the equivalent is the readinessProbe and livenessProbe defined in the Pod spec - Kubernetes does not use Docker health checks directly, but the same Spring Boot Actuator endpoints work for both. Resource limits (--memory and --cpus) prevent container OOM kills from taking down the host and enable fair CPU scheduling across containers.

The below example shows a production-ready Dockerfile for a Spring Boot application with health checks, resource limits, a non-root user, and a .dockerignore file to minimise build context size.


Build and run with resource limits,

# Build the image
docker build -t myapp:1.0.0 .

# Run with memory and CPU limits
docker run -d \
  --name myapp \
  --memory=512m \
  --memory-swap=512m \
  --cpus=0.5 \
  -p 8080:8080 \
  myapp:1.0.0

# Check health status (after 60s start period)
docker ps
CONTAINER ID  IMAGE        STATUS
abc123def456  myapp:1.0.0  Up 2 minutes (healthy)

# View health check history
docker inspect --format "{{json .State.Health}}" myapp
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {"Start": "2024-01-15T10:00:30Z", "End": "2024-01-15T10:00:30Z",
     "ExitCode": 0, "Output": "{status:UP,...}"}
  ]
}

It gives the following output,

docker compose up -d
[+] Running 2/2
  Container mysql  Started (healthy after 20s)
  Container app    Started (healthy after 75s)

# Without health check, app would start before MySQL is ready
# With condition: service_healthy, app waits until MySQL passes its health check

# Resource usage check
docker stats --no-stream
NAME   CPU %  MEM USAGE / LIMIT  MEM %
app    12.3%  210MiB / 512MiB    41.0%
mysql   2.1%  128MiB / 256MiB    50.0%

Container security checklist:

Run as non-root - Always create a dedicated user with adduser and set USER in the Dockerfile. If a vulnerability is exploited, the attacker gets a low-privilege user, not root. Read-only filesystem - Add --read-only flag and mount only the directories the app needs to write to (e.g. /tmp). No secrets in images - Never put passwords or API keys in Dockerfile ENV instructions. Use Docker secrets, Kubernetes Secrets, or environment variables injected at runtime. Scan images - Use docker scout cves myapp:1.0.0 or Trivy to scan for known CVEs in the base image and dependencies before deploying.


 
  


  
bl  br