After mapping ports and networks in Part 8, the next step is checking whether a container is actually serving traffic. Health checks and restart policies solve different failure modes, and beginners often mix them up at first. This post uses nginx and MySQL to separate those ideas clearly.
How this post flows
- What Dockerfile-level health checks do
- How to adjust health checks at the Compose level
- What
restartpolicies mean for automatic recovery - A hands-on lab with nginx and MySQL
- Operational instincts beginners should practice first
Reading card
- Estimated time: 15 minutes
- Prereqs: basic
curl/wgetusage plus Docker Compose fundamentals- After reading: you can read health-check results and tune restart policies.
What problem each feature solves
Before looking at syntax, separate the two failure modes.
- Use a health check when the container process is still running but the service has stopped responding.
- Use a restart policy when the main process exits and you want Docker to start it again automatically.
That means the two features complement each other.
- Health checks catch silent failures. Example: nginx is still running, but the HTTP endpoint stops responding.
- Restart policies catch crashes. Example: the MySQL process exits with an error code.
One important caveat: in standalone Docker or plain Docker Compose, an unhealthy container is mostly informational. Docker updates the status, but it does not restart the container just because the health check failed. In Swarm or Kubernetes, that health signal can trigger replacement or rescheduling.
Dockerfile-level health checks
A health check tells the image how to verify that a running container is healthy. When customizing an nginx image, you might add:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://127.0.0.1/ || exit 1
intervalis how often Docker runs the check.timeoutis the maximum time Docker waits for that check to finish.retriesis how many failed checks in a row change the status tounhealthy.start-periodtells Docker to ignore early failures while the service is still starting.
The command itself still needs to make sense inside the container. Here, wget --spider only tests whether the page responds; it does not download the whole body. The || exit 1 part means "report failure if that command fails."
Once this is in the Dockerfile, every environment that runs the image inherits the rule. For MySQL you could use mysqladmin ping -h 127.0.0.1 to confirm the database accepts connections.
When the check fails retries times in a row, the container status changes to unhealthy, but the main process keeps running. One successful check later, the status returns to healthy.
Compose-level health checks
Compose lets you override or extend those settings per service.
services:
web:
image: my-nginx:latest
ports:
- "8080:80"
healthcheck:
test: ["CMD", "wget", "--spider", "http://127.0.0.1" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Here you reuse the Dockerfile logic but give the runtime more generous timeout and start_period values. That is useful when the same image runs in different environments.
For example, the image might start quickly on your laptop but need longer on a real server because the database initializes more data first. If the service is healthy but the check fails during startup, increase start_period. If the command succeeds eventually but often times out, increase timeout.
What restart: unless-stopped really does
restart policies define how Docker reacts when a container exits unexpectedly. The common options are:
no: never restart automatically.on-failure: restart only when the process exits with a non-zero code.unless-stopped: restart automatically unless the user stops the container manually.always: restart no matter why it stopped.
Use them based on the situation.
- Choose
nofor local debugging when you want the container to stay stopped so you can inspect the failure. - Choose
on-failurewhen a process may crash transiently and you want limited automatic recovery. - Choose
unless-stoppedfor long-running services that should come back after a crash but still respect a manual stop. - Choose
alwaysonly when you really want Docker to restart the service in almost every case.
The key distinction is still this: health checks report service state, while restart policies react to process exit. In plain Docker, a container turning unhealthy does not restart on its own. If you need automatic action on health status, you need an orchestrator that understands that signal.
Lab: nginx and MySQL
Hands-on practice makes the difference visible. If you already have old lab containers, clean them up first.
docker rm -f web db 2>/dev/null || true
Then try two small missions.
-
nginx container
docker run -d --name web --health-cmd "wget --spider -q http://127.0.0.1 || exit 1" \ --health-interval=10s --health-start-period=5s --restart unless-stopped nginx:alpine docker ps --format "table {{.Names}}\t{{.Status}}" docker exec web nginx -s quit docker ps --format "table {{.Names}}\t{{.Status}}"Stop the nginx process on purpose. This time the main process exits, the restart policy brings the container back, and then the health check marks it healthy again. You should see the status move through states like
Restarting,Up ... (health: starting), and finallyUp ... (healthy). -
MySQL container
docker run -d --name db -e MYSQL_ROOT_PASSWORD=pass1234 \ --health-cmd 'mysqladmin ping -h 127.0.0.1 -ppass1234 || exit 1' \ --health-start-period=30s \ --health-interval=20s --restart unless-stopped mysql:8 docker ps --format "table {{.Names}}\t{{.Status}}" docker logs -f db --tail 20During startup,
start-periodtells Docker not to count early failures yet. After MySQL is ready, the ping command confirms that the DB answers. In a real deployment, avoid passing passwords directly on the command line like this; it is fine here only because this is a small local lab.
While practicing, inspect the health details directly.
docker inspect --format '{{json .State.Health}}' web
You will see fields such as Status, FailingStreak, and recent log entries. That makes it easier to distinguish "the process crashed" from "the process is still running but the service check failed."
Operational instincts to remember
Keep these two sentences together in your head:
- Health checks label a container as healthy or unhealthy.
- Restart policies decide what happens after the process exits.
They act on different parts of the lifecycle, so most real services need both. Part 10 explains how a healthy container actually serves web traffic through Nginx.
💬 댓글
이 글에 대한 의견을 남겨주세요