Safeguard
Developer Tools•

How do I write a Docker HEALTHCHECK when the image has no curl?

Use the language runtime or the app binary itself as the check, in exec form. Examples for Node.js, Python and Go, plus the timing defaults to set.

Safeguard Security Team
4 min read

When a minimal image has no curl or shell, write the HEALTHCHECK as an exec form command that runs something already in the image: the language runtime with a one line script, or a health subcommand built into your own binary. Exit code 0 means healthy and 1 means unhealthy.

What Docker documents

The Dockerfile reference says HEALTHCHECK tells Docker how to test whether a container is still working, for example to detect a web server stuck in an infinite loop that still has a running process. A container with a healthcheck has a health status of starting, healthy or unhealthy. The command's exit status sets it: 0 for healthy, 1 for unhealthy, and 2 is reserved.

The defaults for the options are an interval of 30 seconds, a timeout of 30 seconds, a start period of 0 seconds and 3 retries. If a run exceeds the timeout, the check counts as failed and the process is stopped with SIGKILL. Only the last HEALTHCHECK in a Dockerfile takes effect, and output on stdout or stderr is stored, with only the first 4096 bytes kept.

The command can be a shell command or an exec array. In an image with no shell, only the exec array works.

Examples by language

Node.js, using the built in fetch available in current versions:

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD ["node", "-e", "fetch('http://127.0.0.1:3000/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]

Python, using only the standard library:

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD ["python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=2).status == 200 else 1)"]

Go, with a subcommand in your own binary, which suits a static image:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD ["/app", "healthcheck"]

Your main function would handle the healthcheck argument by calling the local endpoint and exiting with 0 or 1.

What a good health endpoint checks

CheckIncludeAvoid
Process responds to HTTPYesNothing to avoid
Can reach its own databaseMaybe, keep it cheapHeavy queries every 30 seconds
Calls third party servicesNoYour container looks dead when a vendor is down
Reveals version or configNoInformation for an attacker

Bind the health route to localhost behaviour where possible, and return a bare status. A health endpoint that prints build details or dependency versions helps an attacker plan.

A security note on health checks

The check runs inside the container with the container's privileges. Keep it small. A check that installs a tool at run time or fetches a script from the internet adds risk for little benefit. If you are on Kubernetes, remember that it ignores the Docker HEALTHCHECK instruction and uses its own probes, so define liveness and readiness probes there as well. Verify this against your platform documentation, since runtimes differ.

Where Safeguard fits

Health checks tell you the process is alive, not that its dependencies are safe. Safeguard covers the dependency side with scanning, SBOMs and continuous monitoring that rechecks shipped builds against new advisories. Try the free scan.

Quick check

Why does my HEALTHCHECK fail with a not found error on a distroless image?

Shell form needs a shell. Use the exec array form, and use a program that exists in the image.

Does a healthy status mean the app is secure?

No. It only means the check command exited with 0.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.