jonnifyelixir
F7 · Deploy to Fly / F7.5 · Portal is live / F7.5.1 · liveness verification

F7.5 · deep dive 1 · liveness is the acceptance

F7.5.1

A returned 200 is the proof

The chapter’s terminal gate is not a file you read — it is a command you run, and its exit code is the verdict. curl -fsS https://echo-portal.fly.dev/health returns the body ok with exit 0, observed over the public internet. A configuration that “looks right” never fails a build; a wrong value fails a boot, and the only thing that separates “deployed correctly” from “deployed wrong” is a live 200 returned across the network. The probe’s exit code is the acceptance — not a config inspection.

We read the as-built /health surface — the route declared outside the :browser pipeline (no session, no CSRF token), the :health action that answers 200 "ok" with no domain call — add a /courses route smoke that proves the application serves, read the -fsS idiom so the exit code is the gate, and close on the standing-liveness handoff: the durable node is the Operator’s deploy, not an agent’s ephemeral boot.

Two probes, two proofs

The chapter ends when a public request returns a 200. The first probe is the liveness stub: curl -fsS https://echo-portal.fly.dev/health. It hits the :health route in PortalWeb.Router — get("/health", PortalWeb.CourseController, :health) — whose action is one line: def health(conn, _params), do: send_resp(conn, 200, "ok"). The response is a 200 with the literal body ok and no domain call; it reaches no context, no store, no engine. That is deliberate — the stub proves the endpoint is up and answering HTTP, and it answers even while a domain dependency is still settling, so it isolates “the node accepts connections” from “the application works.”

The second probe is the route smoke: curl -fsS https://echo-portal.fly.dev/courses. It returns a 200 and the live catalog markup — proving the endpoint serves the application, not only the liveness stub. A stub that answers while every real route 500s is a half-truth; the smoke closes that gap by exercising a path that reaches the running catalog. Two probes, two proofs: /health says “the node is up,” /courses says “the application serves.”

Select a probe below to read what it returns, what it proves, and why both are worth running.

Two probes · two proofs of a live deploy

curl -fsS https://echo-portal.fly.dev/… GET /health PortalWeb.CourseController :health send_resp(conn, 200, "ok") 200 · body ok · no domain call GET /courses the live catalog 200 · catalog markup proves the application serves

The stub proves the node is up; the route smoke proves the application serves — a deploy that passes only the first is up but not yet doing its job.

Why the probe needs no credential

An operator probes the endpoint with a bare curl — no cookie jar, no token, no login step. That works because PortalWeb.Router declares get("/health", PortalWeb.CourseController, :health) outside the :browser pipeline (the router’s own note records the route as the retained domain-free liveness probe). A route inside :browser runs the session plug and the CSRF protection, so a write to it would need a token minted from a prior page load; a route outside that pipeline carries neither. The liveness probe is deliberately placed where a credential-free curl reaches it.

That is why /health — not a page route — is the liveness probe. A page that lives under the :browser pipeline would still render for an operator’s GET, but coupling the liveness contract to a session-bearing surface invites a future CSRF or auth change to silently break the probe. The stub stays small, domain-free, and pipeline-free on purpose: it answers the one question a probe must answer — is the node up? — with the fewest moving parts that can fail.

Select a route placement below to read whether the operator’s bare curl reaches it, and which plugs sit in the way.

Route placement · does a bare curl reach it?

route placement outside :browser session plug in the way? no CSRF token required? no a bare curl (no token) reaches it YES

The probe carries no credential because the route sits outside :browser — that is the reason /health, and not a page route, is the liveness gate.

The exit code is the gate

The -fsS idiom is what turns a curl into a gate. -f (--fail) makes curl exit non-zero on any non-2xx response — and on a connection failure — instead of printing an error page with exit 0. -s (--silent) hides the progress meter, and -S (--show-error) keeps a real error message visible even while silent. Put together: the command stays quiet on success, prints a genuine error on failure, and — the load-bearing part — its exit code reports the result.

That makes the gate a shell one-liner whose branch is decided by the exit status, not by parsing output:

# the prod standing-liveness gate — the exit code is the verdict
curl -fsS https://echo-portal.fly.dev/health >/dev/null \
  && echo "PORTAL LIVE" \
  || echo "PORTAL DOWN"

# -f  exit non-zero on any non-2xx (and on a connection failure)
# -s  silent: no progress meter
# -S  show a real error even while silent
# a returned 200 → exit 0 → "PORTAL LIVE"

Why this is the gate and not a config inspection: a configuration that “looks right” never fails a build — a wrong value fails a boot. The release could carry a host typo, a stale secret, or an adapter that cannot reach its database, and every one of those reads as plausible text on disk. Only a live 200 returned over the public internet distinguishes “deployed correctly” from “deployed wrong,” because only the boot — not the file — exercises the values against reality. The exit code is the one signal that has actually been across the network and back.

a config inspection

reads plausible text on disk — a host, a secret, an adapter pick — and a wrong value still looks right, so it never fails the read.

a returned 200

has crossed the public internet and back; curl -fsS turns the result into an exit code, so the gate cannot be passed by a value that only looks correct.

One honesty about scope: an agent cannot leave this prod endpoint durably running. The durable node is the Fly machine the Operator’s deploy left up, not an agent’s ephemeral boot — F7.5 documents and verifies the probe, and the Operator’s deploy is what makes the endpoint durable. With the result observed, the next dive turns the chapter’s four faults into a triage runbook: F7.5.2 · the four-gotcha triage runbook; then F7.5.3 · day-2 operations keeps it live after the first boot.

References

The -f/-s/-S flags and the non-zero exit that makes the probe a gate, the Fly edge proxy’s HTTP check against /health, the Phoenix pipeline a route does or does not pass through, and the send_resp/3 behind the :health action.

Sources

Related in this course