probes

Kubelet-style liveness / readiness / startup health checks.

probes {} declares container-level health checks. Three orthogonal decisions:

  • liveness — is the container still alive? Failures restart the container.
  • readiness — should the replica receive traffic? Failures drop it from ingress upstreams.
  • startup — has the container finished booting? Gates the readiness aggregation until it passes for the first time.

Accepted on deployment, app, statefulset.

Synopsis

probes {
  startup {
    http_get { path = "/health" port = 8080 }
    initial_delay     = "0s"
    period            = "2s"
    timeout           = "1s"
    failure_threshold = 30
    success_threshold = 1
  }

  liveness {
    http_get { path = "/health" port = 8080 }
    period            = "10s"
    failure_threshold = 3
  }

  readiness {
    http_get { path = "/ready" port = 8080 }
    period            = "5s"
    failure_threshold = 1
    success_threshold = 2
  }
}

Selectors — exactly one per probe

Each probe declares one selector. More than one or zero is rejected.

SelectorUse it for
http_get { path port [scheme] [http_headers] }HTTP services. Returns pass on 200–399.
tcp_socket { port }Anything that opens a port — postgres, redis, mongo, kafka.
exec { command = [...] }Custom logic the container already knows how to run.

http_get

FieldRequiredDefaultMeaning
pathyesMust start with /.
portyesPositive int.
schemeno"http""http" or "https". https uses default TLS verification — self-signed certs fail.
http_headersno{ "X-Probe" = "voodu" } extra request headers.

tcp_socket

FieldRequiredDefaultMeaning
portyesPositive int. Pass = TCP accept.

exec

FieldRequiredDefaultMeaning
commandyesNon-empty argv. Pass = exit 0.

Threshold knobs (per probe)

All durations are strings ("5s", "1m30s").

FieldDefaultMeaning
initial_delay0sWait before the first probe.
period10sTime between probes.
timeout1sPer-probe timeout.
failure_threshold3Consecutive failures → fail.
success_threshold1Consecutive passes → pass. Only meaningful for readiness — liveness and startup are binary signals.

Behavior per probe type

Liveness

Failure threshold trips → voodu runs docker restart on the container. Same container ID; the process restarts in place.

Readiness

Phase transitions update the replica's readiness state. Voodu-caddy reads this state (when integrated) and drops failing replicas from the upstream pool. Rolling deploys gate on readiness — new replicas only receive traffic after passing.

Startup

Short-lived. Gates the readiness aggregation until the first pass — until startup passes once, the replica is reported not ready to ingress so it doesn't receive traffic. Stops itself permanently after the first success.

Note: voodu's current implementation does not mask liveness behind startup. If you set both, a slow-booting container with a failing liveness probe can still be restarted mid-boot. Use initial_delay on liveness as a safety margin for slow starts.

Validation

The apply is rejected when:

  • A probe declares zero selectors.
  • A probe declares more than one selector.
  • http_get.path doesn't start with /.
  • http_get.port or tcp_socket.port ≤ 0.
  • exec.command is empty.

success_threshold values other than 1 are accepted on liveness and startup but have no effect — those signals are binary.

Examples

HTTP service with all three probes

deployment "prod" "api" {
  image = "ghcr.io/myorg/api:1.7"
  ports = ["8080"]

  probes {
    startup {
      http_get { path = "/health" port = 8080 }
      period            = "2s"
      failure_threshold = 30   # 60s budget before declared dead
    }

    liveness {
      http_get { path = "/health" port = 8080 }
      period            = "10s"
      failure_threshold = 3
    }

    readiness {
      http_get { path = "/ready" port = 8080 }
      period            = "5s"
      failure_threshold = 1
      success_threshold = 2
    }
  }
}

Stateful service with TCP probe

statefulset "data" "pg" {
  image = "postgres:16"
  ports = ["5432"]

  probes {
    startup {
      tcp_socket { port = 5432 }
      period            = "2s"
      failure_threshold = 30
    }

    readiness {
      exec { command = ["pg_isready", "-U", "postgres"] }
      period            = "5s"
      failure_threshold = 1
      success_threshold = 2
    }
  }

  volume_claim "data" { mount_path = "/var/lib/postgresql/data" }
}

HTTPS probe with custom header

probes {
  liveness {
    http_get {
      path   = "/-/live"
      port   = 8443
      scheme = "https"

      http_headers = {
        "Authorization" = "Bearer ${PROBE_TOKEN}"
      }
    }
    period = "10s"
  }
}

Disable plugin-default probes (postgres / redis)

The postgres and redis plugins ship default probes. Declaring any probes {} block totally replaces them — no field-level merge. To disable entirely:

postgres "data" "pg" {
  image = "postgres:16"

  probes {}        # empty block = no probes
}

To override one and keep others, redeclare both:

postgres "data" "pg" {
  image = "postgres:16"

  probes {
    liveness  { tcp_socket { port = 5432 } period = "10s" }
    readiness { exec { command = ["pg_isready", "-U", "postgres", "-d", "myapp"] } period = "5s" success_threshold = 2 }
  }
}

Trade-offs

Total override, no merge. Operator-declared probes {} totally replaces what the plugin would have emitted. There's no field-level merge — declaring just liveness {} while expecting plugin defaults for readiness won't work. Copy what you want before overriding.

Liveness restarts; readiness gates traffic. A failed liveness probe is destructive — voodu restarts the container. A failed readiness probe is non-destructive — voodu just drops it from upstreams. Use liveness for hard failures (process deadlocked), readiness for transient unavailability (DB connection lost, warming up).

Startup gates readiness, not liveness. Without a startup probe, ingress can route to a replica before it's ready. With one, the replica stays out of upstream rotation until startup passes once. Liveness is independent — use initial_delay on liveness if your container boots slowly.

success_threshold > 1 is meaningful for readiness only. Liveness and startup are binary — success_threshold > 1 is accepted by the parser but ignored at runtime.

Probe failures don't fail the apply. Apply succeeds if the spec validates; whether the probe ever passes is a runtime concern. Use voodu describe and voodu logs to debug failing probes after apply.

Probe spec is hashed. Editing a probe path, port, or threshold changes the spec hash and triggers a rolling restart on next apply.

Plugin-default probes ship with postgres (TCP + pg_isready) and redis (TCP + redis-cli ping). Both are emitted by the plugin's expand, not merged at the controller — totally replaced by any operator probes {} declaration.

See also

On this page