Home DevOps Docker Healthchecks and Restart Policies: Stop Restarting Dead Containers
Intermediate 3 min · July 11, 2026

Docker Healthchecks and Restart Policies: Stop Restarting Dead Containers

Docker healthchecks and restart policies: how to stop restart loops, detect dead containers, and build self-healing services.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 11, 2026
last updated
258
articles · all by Naren
Before you start⏱ 25 min
  • Basic Docker CLI experience (run, ps, logs)
  • Familiarity with Dockerfile syntax
  • Understanding of container lifecycle
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Add a HEALTHCHECK instruction to your Dockerfile or docker-compose.yml. Set restart policy to 'unless-stopped' for most services. Test health with 'docker inspect' and monitor restart counts with 'docker ps --filter status=restarting'.

✦ Definition~90s read
What is Docker Healthchecks and Restart Policies?

Docker healthchecks are commands that periodically test if a container is working correctly. Restart policies define when Docker should automatically restart a container. Together they form the foundation of self-healing containers.

Think of a healthcheck like a smoke detector that checks your container every 30 seconds.
Plain-English First

Think of a healthcheck like a smoke detector that checks your container every 30 seconds. If it detects trouble, it signals Docker to call the fire department (restart policy). The restart policy is like an automatic fire extinguisher — it puts out the fire and brings the container back to life. Without healthchecks, Docker only knows if the container process crashed, not if it's silently broken.

Your container is running. But is it working? I've seen a payment service that was 'up' for 3 days while silently dropping every transaction. The process was alive. The thread pool was dead. Docker had no idea. That's the lie of 'just check if the process is running'.

Healthchecks solve this by asking the container: 'Are you actually doing your job?' Restart policies then decide: 'Should I try again or give up?' Together they turn fragile containers into self-healing services. Without them, you're flying blind — your monitoring says 'green' while your users see errors.

By the end of this, you'll write healthchecks that catch real failures, configure restart policies that prevent infinite restart loops, and know exactly what to do when your container enters a crash loop at 3 AM.

Why Process Alive Doesn't Mean Service Healthy

Docker's default behavior is naive: if the PID 1 process is alive, the container is 'Up'. But your app can be in a death spiral — deadlocked threads, exhausted connection pools, corrupted state — while the process breathes. I've debugged a Kafka consumer that was 'Up' for 12 hours while consuming zero messages because a deserialization error had thrown an uncaught exception in a callback. The main thread was fine. The business logic was dead.

A healthcheck changes this. It's a command Docker runs periodically inside the container. Exit code 0 means healthy. Exit code 1 means unhealthy. Docker tracks the state and can act on it — restart the container, stop routing traffic, alert your team.

Without a healthcheck, you're trusting the OS process scheduler to tell you if your app works. It won't. Healthchecks are the difference between 'the container is running' and 'the service is serving'.

BasicHealthcheck.dockerfileDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — DevOps tutorial

# A Dockerfile with a basic HTTP healthcheck
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .

EXPOSE 3000

# Healthcheck: curl the /health endpoint every 30s, timeout after 10s
# Start checking after 5s (give app time to boot)
# Mark unhealthy after 3 consecutive failures
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
Output
Container starts. After 5s, healthcheck begins. Every 30s, curl runs. If /health returns non-200, exit code 1 marks container unhealthy. After 3 failures, container status becomes 'unhealthy'.
Production Trap: Healthcheck Command Availability
If your base image doesn't have curl or wget, the healthcheck will fail immediately. Use 'CMD curl -f http://localhost:3000/health || exit 1' but ensure curl is installed. Alpine images need 'apk add --no-cache curl'.

Writing Healthchecks That Catch Real Failures

A healthcheck must test what matters. For a web service, hit an endpoint that exercises the database connection pool, not just a static 'OK' string. I've seen healthchecks that returned 200 while the database was down because the endpoint didn't touch the DB. That's a lying healthcheck.

For a worker process, check that it's processing messages. For a database, run a simple query. The healthcheck should be fast — under 5 seconds ideally. If it takes 30 seconds, you're adding load during a crisis.

Use the 'start-period' flag to give your app time to initialize. Without it, the healthcheck might fail during boot and trigger a restart loop. Set 'retries' to 3-5 to avoid flapping on transient issues.

ProductionHealthcheck.dockerfileDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — DevOps tutorial

# Healthcheck that tests DB connectivity via a dedicated endpoint
FROM node:18-alpine

# Install curl for healthcheck
RUN apk add --no-cache curl

WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .

EXPOSE 3000

# Healthcheck hits /health/db which does a SELECT 1
# If DB is down, this returns 503 and healthcheck fails
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health/db || exit 1

CMD ["node", "server.js"]
Output
Healthcheck runs every 15s. Hits /health/db. If DB connection fails, curl returns non-zero, healthcheck fails. After 3 failures, container marked unhealthy.
Senior Shortcut: Healthcheck as a Load Shedding Signal
When a container becomes unhealthy, a load balancer can stop sending traffic to it. Use 'docker inspect --format="{{json .State.Health}}" <container>' to check health status programmatically. Combine with orchestrator like Docker Swarm or Kubernetes for automatic traffic draining.

Restart Policies: When to Restart and When to Give Up

Restart policies tell Docker what to do when a container exits. The options: 'no' (never restart), 'on-failure' (restart if exit code non-zero), 'always' (always restart, even if you stop it manually), 'unless-stopped' (always restart unless you explicitly stop it).

'always' is dangerous — if your container crashes on startup due to a config error, it will restart forever, burning CPU and filling logs. 'on-failure' with a max retry count is safer. 'unless-stopped' is my default for most services — it survives daemon restarts but respects manual stops.

I once saw a team use 'always' on a container that had a typo in the command. It restarted 10,000 times in an hour, generating gigabytes of logs and costing $500 in CloudWatch bills. Set limits.

RestartPolicies.docker-compose.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — DevOps tutorial

version: '3.8'
services:
  api:
    build: .
    ports:
      - "3000:3000"
    # Restart unless explicitly stopped, max 5 retries on failure
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  worker:
    build: ./worker
    # Restart on failure, but only up to 3 times
    restart: on-failure:3
    depends_on:
      - api
Output
api service restarts unless manually stopped. worker restarts up to 3 times on failure, then stays stopped.
Never Do This: 'restart: always' Without Healthchecks
If your container crashes immediately due to a bug, 'always' will restart it forever. You'll get a restart loop that drowns your logs and burns resources. Always pair 'always' with a healthcheck and a startup grace period.

Combining Healthchecks and Restart Policies for Self-Healing

Healthchecks and restart policies are a tag team. The healthcheck detects unhealthiness. The restart policy decides what to do. But they don't automatically restart on healthcheck failure — that's a common misconception. By default, Docker only restarts on container exit, not on unhealthy status.

To restart on unhealthy, you need an orchestrator like Docker Swarm or Kubernetes, or a custom watcher. In Swarm, set '--restart-condition=any' and the service will restart when healthcheck fails. In Kubernetes, liveness probes restart the pod.

For standalone Docker, you can use a sidecar script that watches 'docker inspect' and restarts the container. But honestly, if you need that, you should be using an orchestrator.

SwarmSelfHealing.docker-compose.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — DevOps tutorial

version: '3.8'
services:
  api:
    image: myapp:latest
    deploy:
      replicas: 3
      restart_policy:
        condition: any  # Restart on exit OR unhealthy
        delay: 5s
        max_attempts: 3
        window: 120s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 15s
      timeout: 5s
      retries: 2
      start_period: 10s
Output
Swarm service with 3 replicas. If healthcheck fails, Swarm restarts the task. Max 3 restart attempts in 120s window.
Interview Gold: Healthcheck vs Liveness Probe
Docker healthchecks are analogous to Kubernetes liveness probes. But Docker's healthcheck doesn't trigger restart by default — it's just a status. Kubernetes liveness probes restart the pod immediately. Know the difference for interviews.

Gotchas from the Trenches: Healthcheck Pitfalls

  1. Healthcheck command not found: If your image lacks curl, the healthcheck fails immediately. Always test your healthcheck command inside the container first.
  2. Healthcheck too slow: A healthcheck that takes 30 seconds during a traffic spike can cause cascading failures. Keep it under 5 seconds.
  3. Healthcheck port mismatch: If your app listens on port 8080 internally but you healthcheck port 3000, it fails. Use 'docker exec' to verify the port.
  4. Start period too short: If your app takes 20 seconds to boot but start-period is 5s, the healthcheck fails during boot and may trigger unwanted restarts.
  5. Restart policy 'always' with healthcheck: If the healthcheck fails but the process doesn't exit, 'always' won't restart. You need an orchestrator or a custom script.
DebugHealthcheck.shDEVOPS
1
2
3
4
5
6
7
8
// io.thecodeforge — DevOps tutorial

# Debug healthcheck: run the same command inside the container
docker exec <container> curl -f http://localhost:3000/health
# Check health status
docker inspect --format='{{json .State.Health}}' <container>
# View healthcheck logs
docker inspect --format='{{json .State.Health.Log}}' <container> | jq .
Output
Output of curl inside container. Health status JSON with Status, FailingStreak, and Log array.
The Classic Bug: Healthcheck on Wrong Port
I've debugged a container that was 'unhealthy' for hours. The healthcheck was curling port 3000, but the app listened on 8080. The healthcheck never succeeded. Always verify with 'docker exec' before deploying.

When Not to Use Healthchecks and Restart Policies

Healthchecks add complexity. For batch jobs that run once and exit, skip them. For containers that are part of a unit test suite, restart policies are pointless — you want them to exit cleanly.

If your app is stateless and crashes on any error, a simple 'on-failure:3' is enough. Don't over-engineer with healthchecks if the process itself is the failure detector.

For stateful services like databases, be careful with restart policies. A database that restarts on failure might come back with corrupted data. Use 'on-failure:2' and alert manually. Healthchecks for databases should be read-only queries that don't affect performance.

DatabaseHealthcheck.dockerfileDEVOPS
1
2
3
4
5
6
7
8
9
10
11
// io.thecodeforge — DevOps tutorial

# PostgreSQL healthcheck: lightweight query
FROM postgres:15

# Healthcheck runs a simple SELECT 1
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=5 \
  CMD pg_isready -U postgres || exit 1

# Or use a custom script that checks replication lag
# HEALTHCHECK --interval=10s CMD /healthcheck.sh
Output
pg_isready checks if PostgreSQL is accepting connections. Returns 0 if ready, 1 if not.
Senior Shortcut: Healthcheck for Database Replication Lag
For read replicas, healthcheck can check replication lag. If lag exceeds threshold, mark unhealthy so the load balancer stops sending traffic. Use a custom script that queries pg_stat_replication.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A Node.js API container would run for 2-3 hours, then become unresponsive. 'docker ps' showed 'Up' status. Monitoring showed 200 OK responses but clients reported timeouts.
Assumption
The team assumed a memory leak and increased memory limit to 4GB.
Root cause
The Node.js event loop was blocked by a synchronous CPU-bound operation (image processing). The process stayed alive but couldn't handle requests. No healthcheck existed, so Docker never knew. The 'HEALTHCHECK --interval=30s CMD curl -f http://localhost:3000/health' would have caught this immediately.
Fix
Added HEALTHCHECK with a 5-second timeout. Moved image processing to a worker thread. Set restart policy to 'on-failure:5' to prevent infinite restart loops if the healthcheck itself fails.
Key lesson
  • A running process does not mean a working service.
  • Always healthcheck from the outside — test what the user experiences.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Container status 'unhealthy' but process is running
Fix
1. Run 'docker inspect --format="{{json .State.Health}}" <container>' to see healthcheck logs. 2. Execute healthcheck command manually with 'docker exec <container> <command>'. 3. Check if the healthcheck endpoint is working (e.g., curl inside container). 4. Verify healthcheck interval, timeout, and retries are appropriate.
Symptom · 02
Container restarting every few seconds (restart loop)
Fix
1. Check 'docker logs <container>' for startup errors. 2. Run 'docker inspect <container>' and look for 'RestartCount'. 3. If using 'always', change to 'on-failure:3' to limit retries. 4. Check if the app crashes due to missing dependencies or config. 5. Increase start period if app takes long to boot.
Symptom · 03
Healthcheck never runs or never succeeds
Fix
1. Verify healthcheck command exists inside container ('docker exec <container> which curl'). 2. Check if the healthcheck port matches the app port. 3. Ensure the healthcheck endpoint returns 200. 4. Check Docker daemon logs for healthcheck errors: 'journalctl -u docker.service | grep health'.
★ Docker Healthchecks and Restart Policies Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Container shows 'Up' but service is down
Immediate action
Check health status
Commands
docker inspect --format='{{json .State.Health}}' <container>
docker exec <container> curl -f http://localhost:3000/health
Fix now
Add a HEALTHCHECK that tests actual functionality, not just process existence.
Container restarting in loop+
Immediate action
View logs and restart count
Commands
docker logs --tail 50 <container>
docker inspect --format='{{.RestartCount}}' <container>
Fix now
Set restart policy to 'on-failure:3' and fix the startup error.
Healthcheck failing with 'executable file not found'+
Immediate action
Check if command exists
Commands
docker exec <container> which curl
docker exec <container> apk add --no-cache curl
Fix now
Install curl in Dockerfile or use a different healthcheck command (e.g., wget, or a script).
Container exits immediately with code 0+
Immediate action
Check if app runs in foreground
Commands
docker logs <container>
docker run -it <image> /bin/sh
Fix now
Ensure your CMD runs the app in foreground (e.g., 'node server.js' not 'npm start' which may fork).
FeatureHealthcheckRestart Policy
PurposeDetect if container is working correctlyDecide whether to restart on exit
TriggerPeriodic command executionContainer exit (any exit code)
Default behaviorNo healthcheckrestart: no (no restart)
Restart on failureNo (unless orchestrator)Yes, based on policy
Use caseCatch silent failures (deadlocks, leaks)Handle crashes and OOM kills
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
BasicHealthcheck.dockerfileFROM node:18-alpineWhy Process Alive Doesn't Mean Service Healthy
ProductionHealthcheck.dockerfileFROM node:18-alpineWriting Healthchecks That Catch Real Failures
RestartPolicies.docker-compose.ymlversion: '3.8'Restart Policies
SwarmSelfHealing.docker-compose.ymlversion: '3.8'Combining Healthchecks and Restart Policies for Self-Healing
DebugHealthcheck.shdocker exec curl -f http://localhost:3000/healthGotchas from the Trenches
DatabaseHealthcheck.dockerfileFROM postgres:15When Not to Use Healthchecks and Restart Policies

Key takeaways

1
A running process does not mean a working service. Always healthcheck from the outside
test what the user experiences.
2
Pair healthchecks with restart policies, but remember
healthchecks don't trigger restarts by default. Use an orchestrator for self-healing.
3
Set restart policy to 'unless-stopped' for most services. Avoid 'always'
it can cause infinite restart loops.
4
Keep healthchecks fast (<5s), test them manually with 'docker exec', and include a start-period to avoid false positives during boot.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Docker's healthcheck interact with restart policies? Does an un...
Q02SENIOR
When would you choose 'restart: unless-stopped' over 'restart: always' i...
Q03SENIOR
What happens if your healthcheck command itself hangs or takes too long?...
Q04JUNIOR
What is the purpose of the 'start-period' flag in a healthcheck?
Q05SENIOR
You have a container that keeps restarting with exit code 137. What does...
Q06SENIOR
How would you design a healthcheck for a message queue consumer that sho...
Q01 of 06SENIOR

How does Docker's healthcheck interact with restart policies? Does an unhealthy container automatically restart?

ANSWER
No. By default, Docker only restarts on container exit, not on healthcheck failure. The healthcheck just sets the container status to 'unhealthy'. To restart on unhealthy, you need an orchestrator like Docker Swarm (with restart_condition: any) or Kubernetes (liveness probe). In standalone Docker, you'd need a custom watcher script.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I add a healthcheck to an existing Docker container?
02
What's the difference between Docker healthcheck and Kubernetes liveness probe?
03
How do I check the health status of a Docker container?
04
What restart policy should I use for a database container?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 11, 2026
last updated
258
articles · all by Naren
🔥

That's Docker. Mark it forged?

3 min read · try the examples if you haven't

Previous
Docker Compose for Production
25 / 32 · Docker
Next
Docker Image Security Scanning