This error occurs when a Docker container's HEALTHCHECK command fails, returning exit code 1 instead of 0. The fix involves debugging the health check command, adjusting timing parameters, and ensuring the target service is properly responding.
The "health check command returned non-zero exit code: 1" error in Docker indicates that the HEALTHCHECK instruction defined in your Dockerfile or docker-compose.yml has failed. Docker's health check mechanism periodically runs a command inside your container to verify the service is working correctly. When the health check command exits with code 0, Docker marks the container as "healthy." When it exits with code 1 (or any non-zero code), Docker marks it as "unhealthy." This status is visible in `docker ps` output and can be used by orchestrators like Docker Swarm or Kubernetes to make decisions about container lifecycle. Common reasons for this error include: the health check endpoint not being ready yet during container startup, incorrect health check command syntax, the target service crashing or becoming unresponsive, or network/DNS issues preventing the health check from reaching the service.
First, check the current health status and recent health check output:
# View container health status
docker ps
# Get detailed health check information
docker inspect --format='{{json .State.Health}}' <container_name> | jq
# View the last 5 health check results
docker inspect <container_name> | jq '.State.Health.Log[-5:]'The output shows the exit code and any output/error from each health check attempt. This is crucial for understanding why the check is failing.
Example output:
{
"Status": "unhealthy",
"FailingStreak": 3,
"Log": [
{
"Start": "2024-01-15T10:00:00.000Z",
"End": "2024-01-15T10:00:01.000Z",
"ExitCode": 1,
"Output": "curl: (7) Failed to connect to localhost port 8080"
}
]
}Run the health check command directly inside the container to debug it:
# Get a shell in the running container
docker exec -it <container_name> sh
# Run your health check command manually
curl -f http://localhost:8080/health
echo $? # Check the exit codeIf you can't access a shell, run the command directly:
docker exec <container_name> curl -f http://localhost:8080/healthCommon issues to look for:
- Connection refused: Service not running or wrong port
- 404 Not Found: Endpoint doesn't exist
- Timeout: Service too slow or network issues
- Command not found: curl/wget not installed in image
If the health check fails during container startup, add a start-period to give your application time to initialize:
In Dockerfile:
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1In docker-compose.yml:
services:
web:
image: myapp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
start_period: 60s
retries: 3The start_period gives your application time to start up before health checks begin counting failures. During this period, failed health checks don't count toward the retry limit.
Recommended values:
- interval: 10-30 seconds
- timeout: 5-10 seconds
- start_period: Time your app needs to fully start (30s-5m depending on app)
- retries: 3-5 attempts
Ensure your health check command is correctly formatted:
Use shell form for commands with pipes or || exit 1:
# Correct - shell form
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
# Correct - explicit shell form
HEALTHCHECK CMD ["sh", "-c", "curl -f http://localhost:8080/health || exit 1"]The "|| exit 1" is important because curl and other tools may return exit codes other than 1 on failure. Docker only recognizes 0 (healthy) and 1 (unhealthy) - other exit codes are undefined behavior.
For docker-compose.yml:
healthcheck:
# Shell form (string) - automatically uses shell
test: curl -f http://localhost:8080/health || exit 1
# Or CMD-SHELL explicitly
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
# Exec form - no shell, so || won't work
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]Note: When using exec form (brackets without CMD-SHELL), there's no shell to handle ||, so the exit code passes through directly.
Health checks run inside the container, so they must use localhost or 127.0.0.1, not the external hostname or container name:
# Correct - use localhost inside the container
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
# Wrong - container name only works from other containers
HEALTHCHECK CMD curl -f http://myservice:8080/health || exit 1Verify the port is correct and the service is bound to all interfaces (0.0.0.0) or localhost:
# Check what ports are listening inside the container
docker exec <container_name> netstat -tlnp
# or
docker exec <container_name> ss -tlnpIf your app binds to a specific interface, make sure the health check uses that address.
If curl or wget isn't available in your container, you have several options:
Option 1: Install curl in Dockerfile
# For Debian/Ubuntu
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# For Alpine
RUN apk add --no-cache curlOption 2: Use wget (often already installed)
HEALTHCHECK CMD wget --spider -q http://localhost:8080/health || exit 1Option 3: Use native tools for minimal images
# For images with only basic tools, check if TCP port is open
HEALTHCHECK CMD nc -z localhost 8080 || exit 1
# Or use /dev/tcp in bash
HEALTHCHECK CMD bash -c 'echo > /dev/tcp/localhost/8080' || exit 1Option 4: Add a custom health check script
COPY healthcheck.sh /healthcheck.sh
RUN chmod +x /healthcheck.sh
HEALTHCHECK CMD /healthcheck.shConfigure your application to provide a proper health check endpoint:
Node.js/Express example:
app.get('/health', (req, res) => {
// Check database connection, dependencies, etc.
const isHealthy = checkDependencies();
if (isHealthy) {
res.status(200).send('OK');
} else {
res.status(503).send('Service Unavailable');
}
});Python/Flask example:
@app.route('/health')
def health():
try:
# Check dependencies
db.session.execute('SELECT 1')
return 'OK', 200
except Exception:
return 'Unhealthy', 503For database containers:
# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5Understanding health check exit codes: Docker recognizes only two exit codes for health checks:
- 0: Success - container is healthy
- 1: Failure - container is unhealthy
Any other exit code (like curl's 22 for HTTP 4xx/5xx or 7 for connection refused) should be normalized to 1 using || exit 1.
Health check with pipefail: If your health check uses pipes, the exit code comes from the last command by default. Use set -o pipefail to fail if any command in the pipe fails:
HEALTHCHECK CMD ["sh", "-c", "set -o pipefail && curl -s http://localhost:8080/health | grep -q 'OK' || exit 1"]Using depends_on with health checks: In Docker Compose, you can make services wait for dependencies to be healthy:
services:
web:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5Auto-healing unhealthy containers: Docker doesn't automatically restart unhealthy containers. Options include:
1. Use Docker Swarm with replicas (it replaces unhealthy containers)
2. Use the willfarrell/autoheal container to monitor and restart unhealthy containers
3. Use Kubernetes liveness probes which do restart unhealthy pods
Debugging health checks in CI/CD: If health checks fail in CI but work locally:
- CI environments may have slower startup times - increase start_period
- Network restrictions may block health check requests
- Resource limits may cause timeouts - increase timeout value
Health check vs readiness: Health checks only determine if a container is working, not if it's ready to serve traffic. For Kubernetes, use both liveness (restart if unhealthy) and readiness (stop sending traffic if not ready) probes.
image operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub