---
title: Healthcheck probe
description: A dependency free healthcheck binary for container images that have no shell and no curl.
order: 3
---

Feature: `probe`.

A hardened image (distroless or scratch) has no shell and no `curl`, so a container
healthcheck needs a binary of its own. The `healthprobe` binary sends one `GET` to a
loopback address and exits by the answer. It uses the standard library only: the feature
adds no dependency, and the binary is a few hundred kilobytes.

## Build it into the image

```dockerfile
FROM rust:1.85 AS build
RUN cargo install ops-sdk --registry ops --features probe --bin healthprobe --root /out
# ... build your service in the same stage ...

FROM gcr.io/distroless/cc-debian12
COPY --from=build /out/bin/healthprobe /usr/local/bin/healthprobe
COPY --from=build /src/target/release/web /usr/local/bin/web
HEALTHCHECK CMD ["/usr/local/bin/healthprobe", "http://127.0.0.1:8080/healthz"]
CMD ["/usr/local/bin/web"]
```

The image build needs the registry configuration from the [install steps](index.md) to
fetch the crate.

## Usage

```sh
healthprobe <http://host:port/path> [timeout-seconds]
```

| exit code | meaning |
| --- | --- |
| 0 | healthy: the answer's status is below 400. A redirect counts as healthy |
| 1 | unhealthy: a bad status, a refused connection, a timeout or a malformed answer |
| 2 | called wrongly: no URL, or a timeout that is not a whole number above zero |

The default timeout is 4 seconds for connecting, sending and reading together.

It speaks plain HTTP to a loopback address only: no TLS, no redirects, no proxy and no
authentication. A healthcheck runs inside the container, so none of those are needed.

## From your own code

The same check is a library call:

```rust
use std::time::Duration;
use ops_sdk::probe::{DEFAULT_TIMEOUT_SECS, probe};

let status = probe("http://127.0.0.1:8080/healthz", Duration::from_secs(DEFAULT_TIMEOUT_SECS))?;
```

`probe` returns the HTTP status, or a sentence saying why there was none.
