Quick answer
A Docker health check runs a command inside a container on a schedule to see whether the application is actually working, rather than merely still running. Add a HEALTHCHECK instruction to the Dockerfile, or add a healthcheck section to the Compose service. The command needs to return exit code 0 when the container is healthy and a nonzero exit code when it isn’t.
For an HTTP service listening on port 8080, this is a practical Dockerfile configuration:
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl --fail --silent http://localhost:8080/health || exit 1Make sure the image has curl installed, and that the application exposes a lightweight health endpoint.
What you’ll learn
- How Docker decides a container’s health
- Adding a health check to a Dockerfile
- Configuring and overriding health checks in Docker Compose
- Waiting for a dependency to become healthy
- Inspecting health-check results and tracking down failures
Prerequisites
- Docker Engine with the Docker Compose plugin
- An existing Dockerfile or Compose project
- A command inside the container that can test the application
A health check runs inside the container. A tool installed only on the Docker host won’t be available to the check.
How Docker health checks work
Without a health check, Docker can tell you that the container process is running. That’s not proof the application can accept requests. A web server might stay up while its worker pool, database connection, or internal initialization has failed.
Once a health check is configured, the container gets one of three health states:
| State | Meaning |
|---|---|
starting | The first checks are still running, or the start period has not finished. |
healthy | The latest check finished successfully. |
unhealthy | The configured number of checks failed in a row. |
An unhealthy status does not restart a standalone Docker container on its own. An orchestrator, monitoring system, or some other automation has to handle restart behavior.
Step-by-step instructions
1. Create a useful application health endpoint
For an HTTP application, expose a small endpoint such as /health or /ready. It should return a successful status only once the service is ready to handle requests.
Don’t return large responses or run expensive diagnostics every time the check runs. Health checks can run several times a minute for each container replica.
2. Add HEALTHCHECK to the Dockerfile
Here’s an example that checks a service on port 8080 every 30 seconds:
FROM alpine:3.20
RUN apk add --no-cache curl
WORKDIR /app
COPY . .
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl --fail --silent http://localhost:8080/health || exit 1
CMD ["./start-server"]Those timing options tell Docker when to run the command and how to judge the result:
| Option | Purpose |
|---|---|
--interval=30s | Runs the check every 30 seconds. |
--timeout=5s | Fails a check that takes more than five seconds. |
--start-period=20s | Gives the application time to initialize before failures count toward unhealthy status. |
--retries=3 | Marks the container unhealthy after three consecutive failures. |
3. Configure a health check in Docker Compose
Compose can define a health check even if the image doesn’t include one:
services:
api:
build: .
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20sA Compose health check replaces the image’s Dockerfile health check for that service. To turn off a check inherited from an image, use this:
services:
api:
image: example/api:latest
healthcheck:
disable: true4. Wait for a dependency to become healthy
Short-form depends_on sets startup order, but it doesn’t wait for a dependency to be ready. Use the service_healthy condition when an application needs to wait for a database health check:
services:
db:
image: postgres:16
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: change-me
POSTGRES_DB: appdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
api:
build: .
depends_on:
db:
condition: service_healthyThis helps coordinate startup, though the application should still retry database connections. A dependency can become unavailable after startup.
5. Build and start the containers
docker compose up --build -dThen check the status column:
docker compose psA working service should eventually show healthy. While the application initializes, it may briefly show health: starting.
CMD and CMD-SHELL
The exec form runs the executable directly:
test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]The shell form can use pipes, environment-variable expansion, and operators including ||:
test: ["CMD-SHELL", "curl --fail --silent http://localhost:8080/health || exit 1"]Use CMD when shell features aren’t needed. It skips shell parsing and works with images that don’t contain a shell. Save CMD-SHELL for checks that genuinely need shell behavior.
Other Docker health check examples
PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5The doubled dollar signs tell Compose to pass the variables into the container instead of substituting them while it parses the Compose file.
MySQL
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$${MYSQL_ROOT_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 5Be careful with credentials in command arguments. Process details and container configuration may expose them. For production environments, use a purpose-built script or protected configuration.
Common mistakes
- Checking the published host port: The check runs inside the container, so use its internal port and, usually,
localhost. - Using a missing utility: Minimal images often leave out
curl,wget, and database clients. - Checking only whether a process exists: A process can be running and still unable to serve traffic.
- Setting too short a timeout: Temporary load can cause false failures and unstable status changes.
- Putting complex logic in Compose: Put long checks in a script, copy it into the image, then test that script on its own.
- Assuming unhealthy means restarted: Docker records the status, but it won’t restart a standalone container solely because it is unhealthy.
Best practices
- Keep the check fast, local, and deterministic.
- Set a start period that reflects realistic application startup time.
- Require multiple failures before marking a container unhealthy.
- Test critical readiness without calling every external dependency.
- Don’t log normal successful checks at an error level.
- Use separate liveness and readiness behavior when deploying to an orchestrator that supports both.
- Keep application-level retry logic even when Compose coordinates startup.
Verify and troubleshoot container health
Inspect the current status along with recent check output:
docker inspect --format='{{json .State.Health}}' container_nameFor a quick status-only result, run:
docker inspect --format='{{.State.Health.Status}}' container_nameIf the container is unhealthy, run the check manually from inside it:
docker exec -it container_name curl --fail --verbose http://localhost:8080/healthCheck the application logs too:
docker logs --tail 100 container_nameRunning the command manually will usually show whether you’re dealing with a missing executable, wrong port, DNS failure, authentication issue, slow response, or application error.