What's Actually Happening

Docker container health status remains stuck at "starting" or "unhealthy" and never transitions to "healthy". The healthcheck command either never runs, fails repeatedly, or takes too long to complete.

The Error You'll See

Container stuck at starting:

```bash $ docker ps

CONTAINER ID IMAGE STATUS abc123 my-app (health: starting) Up 10 minutes

# Status never changes from "starting" ```

Container always unhealthy:

```bash $ docker inspect my-app --format '{{.State.Health.Status}}'

unhealthy

$ docker inspect my-app --format '{{json .State.Health}}' | jq

{ "Status": "unhealthy", "FailingStreak": 10, "Log": [ { "ExitCode": 1, "Output": "Health check failed", "End": "2026-04-16T00:45:00Z" } ] } ```

Health events:

```bash $ docker events --filter container=my-app

2026-04-16T00:45:00Z health_status: unhealthy # No transition to healthy ```

Why This Happens

  1. 1.Healthcheck command fails - Returns non-zero exit code
  2. 2.Timeout too short - Healthcheck takes longer than timeout
  3. 3.Interval too long - Not checking frequently enough
  4. 4.Start period insufficient - App needs more time to start
  5. 5.Command missing - Healthcheck not defined in image
  6. 6.Retries too few - Transitions unhealthy too quickly

Step 1: Check Container Health Status

```bash # Check container health status docker inspect my-app --format '{{.State.Health.Status}}'

# Get full health details docker inspect my-app --format '{{json .State.Health}}' | jq

# Check health log (last check results) docker inspect my-app --format '{{json .State.Health.Log}}' | jq

# Check exit codes from healthcheck docker inspect my-app --format '{{range .State.Health.Log}}{{.ExitCode}} {{end}}'

# Check health output docker inspect my-app --format '{{range .State.Health.Log}}{{.Output}}{{end}}'

# View recent health events docker events --filter container=my-app --filter event=health_status ```

Step 2: Check Healthcheck Configuration

```bash # Check healthcheck defined in image docker inspect my-app:latest --format '{{json .Config.Healthcheck}}' | jq

# Example output: { "Test": ["CMD", "curl", "-f", "http://localhost/health"], "Interval": 30000000000, # 30 seconds (nanoseconds) "Timeout": 5000000000, # 5 seconds "StartPeriod": 60000000000, # 60 seconds "Retries": 3 }

# Check container override docker inspect my-app --format '{{json .Config.Healthcheck}}' | jq

# If empty, no healthcheck defined # Add in docker-compose.yml or Dockerfile ```

Step 3: Test Healthcheck Command Manually

```bash # Get healthcheck command from image docker inspect my-app:latest --format '{{range .Config.Healthcheck.Test}}{{println .}}{{end}}'

# Run healthcheck command manually docker exec my-app curl -f http://localhost/health

# Check exit code docker exec my-app curl -f http://localhost/health; echo "Exit code: $?"

# Common exit codes: # 0 = healthy # 1 = unhealthy (command failed) # 2 = reserved (don't use)

# Test with verbose output docker exec my-app curl -v http://localhost/health

# Check if command exists in container docker exec my-app which curl docker exec my-app ls /usr/bin/curl ```

Step 4: Fix Healthcheck Command

```bash # If command not found, install curl or use alternative

# In Dockerfile: HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ CMD curl -f http://localhost/health || exit 1

# Or use wget if curl not available: HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1

# Or use Python: HEALTHCHECK CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost/health')" || exit 1

# Or use nc (netcat): HEALTHCHECK CMD nc -z localhost 8080 || exit 1

# Or use shell script: HEALTHCHECK CMD /healthcheck.sh

# Create healthcheck.sh: #!/bin/sh curl -f http://localhost/health || exit 1 ```

Step 5: Increase Healthcheck Timeout

```bash # If healthcheck takes too long, increase timeout

# In docker-compose.yml: services: my-app: healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] timeout: 10s # Increase from default 5s interval: 30s retries: 3

# Or in Dockerfile: HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD curl -f http://localhost/health

# Test actual healthcheck duration time docker exec my-app curl -f http://localhost/health

# Set timeout to be at least 2x actual duration ```

Step 6: Adjust Start Period

```bash # App may need time to initialize before healthcheck starts

# Increase start period in docker-compose.yml: healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] start_period: 120s # 2 minutes before healthcheck counts failures interval: 30s timeout: 5s retries: 3

# In Dockerfile: HEALTHCHECK --start-period=120s --interval=30s --timeout=5s --retries=3 \ CMD curl -f http://localhost/health

# During start period, failures don't count toward unhealthy status # After start period, retries start counting ```

Step 7: Adjust Retries

```bash # If container marked unhealthy too quickly, increase retries

# In docker-compose.yml: healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] retries: 5 # Require 5 consecutive failures before unhealthy interval: 30s timeout: 5s

# In Dockerfile: HEALTHCHECK --retries=5 --interval=30s --timeout=5s \ CMD curl -f http://localhost/health

# Default retries is 3 # Increase for applications with intermittent health issues ```

Step 8: Fix Application Health Endpoint

```bash # Healthcheck depends on application returning 200 OK

# Test endpoint from inside container docker exec my-app curl -v http://localhost/health

# Expected response: HTTP/1.1 200 OK

# If returns 404, endpoint doesn't exist: # Add health endpoint to application

# Example Node.js health endpoint: app.get('/health', (req, res) => { res.status(200).send('OK'); });

# Example Python Flask: @app.route('/health') def health(): return 'OK', 200

# Example Go: http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write([]byte("OK")) })

# Test application listening on correct port docker exec my-app netstat -tlnp | grep 8080 ```

Step 9: Disable Healthcheck Temporarily for Debug

```bash # Disable healthcheck to debug container

# Stop container with healthcheck docker stop my-app

# Start without healthcheck docker run --no-healthcheck my-app:latest

# Or in docker-compose, disable: services: my-app: healthcheck: disable: true

# Debug container state docker exec my-app ps aux docker exec my-app curl http://localhost/health

# Once fixed, re-enable healthcheck ```

Step 10: Monitor Healthcheck Events

```bash # Monitor health status changes docker events --filter event=health_status

# Watch container health watch -n 5 'docker inspect my-app --format "{{.State.Health.Status}} - Failing: {{.State.Health.FailingStreak}}"

# Create monitoring script cat << 'EOF' > monitor_health.sh #!/bin/bash CONTAINER=my-app

echo "=== Container Health ===" docker inspect $CONTAINER --format 'Status: {{.State.Health.Status}}' docker inspect $CONTAINER --format 'Failing streak: {{.State.Health.FailingStreak}}'

echo "" echo "=== Last 5 Health Checks ===" docker inspect $CONTAINER --format '{{json .State.Health.Log}}' | jq '.[-5:]'

echo "" echo "=== Health Events ===" docker events --filter container=$CONTAINER --filter event=health_status --since 5m --until 0s EOF

chmod +x monitor_health.sh

# Run monitoring ./monitor_health.sh ```

Docker Healthcheck Settings Reference

SettingDefaultPurpose
interval30sTime between healthchecks
timeout30sMax time for healthcheck to complete
start-period0sGrace period before counting failures
retries3Consecutive failures for unhealthy

Verify the Fix

```bash # After fixing healthcheck configuration

# 1. Restart container docker restart my-app

# 2. Wait start period sleep 60

# 3. Check health status docker inspect my-app --format '{{.State.Health.Status}}' // Should show "healthy"

# 4. Verify health log docker inspect my-app --format '{{json .State.Health.Log}}' | jq '.[-1].ExitCode' // Should be 0

# 5. Monitor failing streak docker inspect my-app --format '{{.State.Health.FailingStreak}}' // Should be 0

# 6. Check health events docker events --filter container=my-app --filter event=health_status // Should show: health_status: healthy ```

  • [Fix Docker Healthcheck Always Unhealthy](/articles/fix-docker-healthcheck-always-unhealthy)
  • [Fix Docker Container Restarting Loop](/articles/fix-docker-container-restarting-loop)
  • [Fix Docker Container Not Starting](/articles/fix-docker-container-not-starting)