You hardened your image. Your health check died with it.
You moved to scratch / distroless / Wolfi / Chainguard to shrink the attack
surface, and the same move that removed the shell and curl broke every
HEALTHCHECK you had. hc is the one binary you add back: no shell, no curl, no
libc, no config. Copy it in, point it at a target. Its exit code is Docker's
contract: 0 healthy, 1 unhealthy.
Minimal images are more secure. They also delete the tools your health checks lean on: no shell, no curl, no package manager. So the check every tutorial hands you has nothing to run:
# no curl in the image, so this never runs
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1A secure image that can't tell anyone whether it's alive.
COPY --from=ghcr.io/moq77111113/hc /hc /hc
HEALTHCHECK CMD ["/hc", "http://localhost:8080/health"]That's the whole integration. No sidecar, no libc, no per-service check script.
The URL scheme picks the probe:
hc http://localhost:8080/health # healthy on 2xx-3xx
hc tcp://localhost:6379 # healthy if the TCP connection opens
hc postgres://localhost:5432 # healthy if PostgreSQL accepts connections
hc mysql://localhost:3306 # healthy if MySQL answers its handshake| Scheme | Healthy when |
|---|---|
http / https |
response status is 2xx or 3xx |
tcp |
the TCP connection is established |
postgres / pg |
the server answers the readiness handshake (no auth) |
mysql |
the server sends its handshake greeting (no auth) |
redis |
the server answers PING (+PONG or -NOAUTH) |
amqp |
the broker answers the AMQP 0-9-1 handshake |
grpc |
the gRPC health service reports SERVING |
https checks that the service answers over TLS; it does not validate the
certificate. hc reports liveness, not cert validity, so it works against internal
and self-signed endpoints.
Set a deadline with -timeout (default 3s):
hc -timeout 1s http://localhost:8080/healthThe pain doubles with third-party hardened images like Keycloak, Postgres, or a
DHI base whose Dockerfile you don't control. You can't add a HEALTHCHECK
without forking the image and maintaining your own copy forever.
Don't. Inject hc through a shared volume: the target image is never modified,
and it all runs non-root.
# kubernetes 1.33+: mount hc from its image, read-only
volumes:
- name: healthz
image: { reference: ghcr.io/moq77111113/hc-core }
containers:
- name: app # third-party image, untouched
volumeMounts:
- { name: healthz, mountPath: /healthz, readOnly: true }
livenessProbe:
exec: { command: ["/healthz/hc", "tcp://localhost:8080"] }Older clusters and Docker Compose seed the volume with hc install /healthz/hc
(a scratch image has no cp). Manifests for image-volume, initContainer, and
Compose live in deploy/.
The default binary carries every probe. Need less surface? Pull a variant, and the probes you don't use aren't compiled in:
| Binary | Schemes |
|---|---|
hc-core |
http, https, tcp |
hc-sql |
tcp, postgres, mysql |
COPY --from=ghcr.io/moq77111113/hc-sql /hc /hcAs a build stage (recommended):
COPY --from=ghcr.io/moq77111113/hc /hc /hcOr grab a release binary (named
hc_linux_amd64, hc_linux_arm64, and the hc-core / hc-sql variants), or
from source:
go install github.com/Moq77111113/hc@latestAsk any binary what it is:
hc versionNeed a scheme that isn't here? Open an issue, or send a PR.
hc answers one question: "is this endpoint alive?". No monitoring, no retries,
no state, no metrics, no history. Want those? Wrong binary, on purpose.
MIT