Docker Health Checks¶
The Defense Information Systems Agency (DISA) requires a Docker health check to monitor the health of applications running inside containers. This check validates that services are running and responsive, ensuring continued availability.
Game Warden recommends implementing a healthcheck command directly in your container image to meet this requirement.
As an alternative, you may use a Kubernetes Liveness Probe in place of a Docker healthcheck—but this must be justified in Fidnings to remain compliant.
DISA specification
From Container Image Creation and Deployment Guide (Section 2.6):
Ensuring a container service is still executing and handling workloads is necessary to validate service availability. Adding the health check instruction, HEALTHCHECK, within Docker, to the container image, provides a mechanism for the container platform to periodically check the running container instance against that instruction to ensure the instance is still working. Based on the reported health status, the container platform can then exit a non-working container and instantiate a new one bringing the service back to an available state. Short lived containers that do not require a health check can be submitted for a waiver.
IA Control: SC-5 CCI: CCI-002385
What is a Docker health check command?¶
The Docker healthcheck command is embedded in your container image and runs at set intervals to determine if the container is healthy. If the check fails, the container is marked unhealthy, and depending on configuration, it may be restarted.
Important
- The Department of War (DoW) requires a health check even though newer versions of Docker do not enforce it.
- In Game Warden, missing health checks may appear as
Healthcheck not foundin scan results due to Anchore compliance scanning. - Iron Bank base images do not include a
healthcheckby default.
Health check options in Game Warden¶
You can meet DISA’s requirement in two ways:
- Docker
healthcheck(recommended) - Add theheathcheckinstruction directly to your Dockerfile. -
Kubernetes liveness probe (acceptable alternative) - If you use a liveness probe instead, document this in Findings:
- In Vulnerability dropdown, select Policy N/A.
- In Justification, enter:
Using Kubernetes liveness probe.
Common implementation methods¶
Below is a Dockerfile that includes a healthcheck command using the curl command.
FROM python:3.8-slim
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s \
CMD curl --fail localhost:8080/health || exit 1
EXPOSE 8080
CMD ["python", "app.py"]
curl command every 10 seconds to verify localhost:8000/health. Mark the container unhealthy if the command fails.
You can use wget to add the health check to the Dockerfile:
FROM alpine:3.12
RUN apk update && apk add --no-cache wget
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s \
CMD wget --spider http://localhost || exit 1
--spider).
A Kubernetes liveness probe checks the health of a container and determines whether or not it should be restarted.
To use a liveness probe in Kubernetes, you must define a pod that uses the image then configure a liveness probe that runs the healthcheck command.
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
containers:
- name: my-app-container
image: my-app-image
livenessProbe:
exec:
command:
- curl
- localhost:8080/health
initialDelaySeconds: 15
periodSeconds: 10
Below is an example of a liveness probe using wget:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: my-container
image: my-image
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
Distroless containers are minimal and lack shells or common utilities such as curl or wget.
Doing health checks in these containers require different approaches:
- Mini Web Server in a JAR – Serve a
/healthendpoint and run a Java-based probe. - wget from BusyBox – Use a multi-stage build to copy
wgetinto the container before adding a health check.
This method performs a health check by running a small, lightweight web server inside your container.
Create a mini web server
Similar to a standard web server, it serves content over HTTP. This server will respond to requests that indicate the container’s health.
Bundle the mini web server into a Java ARchive (JAR)
A JAR is similar to a .zip file—it compresses and packages multiple Java classes and resources into a single file. This approach allows you to reuse common features across multiple applications without rewriting code.
For example, you can add health check logic to a Java class, include it in a JAR, and run it in any containerized application.
Build the container
Run the following commands to compile the classes, create the JAR files, and build your Docker image:
javac TheComSunNetHttpServer.java
jar -c -e TheComSunNetHttpServer -v -f TheComSunNetHttpServer.jar TheComSunNetHttpServer.class
javac HealthCheck.java
jar -c -e HealthCheck -v -f HealthCheck.jar HealthCheck.class
docker build -f Dockerfile-java . -t java-healthcheck
Start the container
Launch the container in the background, listening on port 8080:
docker run -d -p 8080:8080 java-healthcheck
sleep 2
curl http://localhost:8080
Verify the health check
Confirm the container is running and healthy:
docker ps
Example output:
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
009a72a63438 java-healthcheck "/usr/bin/java -jar …" 52 seconds ago Up 51 seconds (healthy) 0.0.0.0:8080->8080/tcp friendly_cannon
In this example, you'll use a multistage Docker build to copy wget from BusyBox before running it as part of your health check.
- Why multistage builds? They allow you to separate the Docker build process into multiple steps, keeping the final image smaller and more secure.
- Why BusyBox? Known as the “Swiss Army knife” for Unix/Linux, BusyBox packages many common Linux utilities—such as
cp,rm, andmkdir—into a single lightweight executable. These tools are not available by default in distroless containers, making BusyBox a useful source for adding them.
Build the container
Run the following command:
javac TheComSunNetHttpServer.java
jar -c -e TheComSunNetHttpServer -v -f TheComSunNetHttpServer.jar TheComSunNetHttpServer.class
docker build -f Dockerfile-wget . -t wget-healthcheck
Start the container
Launch the container in the background, listening on port 8080:
docker run -d -p 8080 wget-healthcheck
sleep 2
curl http://localhost:8080
Verify the health check
Confirm the container is running and healthy:
docker ps
Example output:
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
009a72a63438 wget-healthcheck "/usr/bin/java -jar …" 52 seconds ago Up 51 seconds (healthy) 0.0.0.0:8080->8080/tcp friendly_cannon
Java-based health check in a Distroless container
The Java-based health check for Docker method demonstrates how to perform a health check without relying on additional utilities such as curl or wget.
This approach uses:
- Java 11 single-file program — allows you to run a .java file directly without compiling with
javac. - Java HttpClient — sends HTTP requests and validates responses.
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
public class HealthCheck {
public static void main(String[] args) throws InterruptedException, IOException {
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/actuator/health"))
.header("accept", "application/json")
.build();
var response = client.send(request, BodyHandlers.ofString());
if (response.statusCode() != 200 || !response.body().contains("UP")) {
throw new RuntimeException("Healthcheck failed");
}
}
}
- Sends a
GETrequest to/actuator/health. -
Checks if:
- The HTTP status code is 200 (OK).
- The response body contains "UP", indicating a healthy service.
-
Throws a
RuntimeExceptionif either check fails; otherwise, it exits silently.
Docker image best practices¶
-
Expose a health endpoint that returns 200 only when the app is actually ready (connections open, migrations done, deps reachable). This enables safe autoscaling and zero(ish)-downtime deploys.
Example: App exposes
/healthzand returns 200 only when readyHEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -fsS http://localhost:8080/healthz || exit 1 -
Never run processes as root inside containers.
Example: Create unprivileged user
RUN addgroup -S app && adduser -S app -G app USER app -
Each
RUN,COPY,ADDmakes a layer. Combine related commands and clean caches to shrink images. - Exclude anything not needed at runtime/build (node_modules, tests, .git, IDE files, secrets).
- Use immutable, descriptive tags (e.g., 1.4.3, 2025.10.01-sha.abcd123). This makes rollbacks/audits reliable.
- Never bake API keys, tokens, or certs into layers or files. Inject via env vars/secret stores at deploy. CI scanners (e.g., Trivy) should fail the build if secrets are detected.
- Build artifacts in one stage; ship only what’s needed in the final image.
- Choose distroless/Alpine/Chainguard images when possible; avoid large bases unless you truly need them. If you only need to serve static files or a single binary, start from scratch (0-byte base). Smaller images lead to faster pulls and a smaller attack surface.
- If you only need to serve static files or a single binary, start from scratch (0-byte base).