F7.5 · deep dive 1 · liveness is the acceptance
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
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?
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
- curl — the manual —
-f/-s/-Sand the non-zero exit on a non-2xx response that makes the probe a gate. - Fly.io — Health checks — the HTTP check the edge proxy runs against
/health. - Phoenix — Phoenix.Router — pipelines and routes; a route outside
:browsercarries no session or CSRF plug. - Plug — Plug.Conn —
send_resp/3, the 200-with-body the:healthaction returns.
Related in this course
- F7.5.2 · the four-gotcha triage runbook — the symptom-to-fix decision tree, with the cross-machine discriminator.
- F7.5.3 · day-2 operations — logs, status, proxy-and-
psqlinspection, re-migration idempotency, rollback. - F7.5 · Portal is live — the module hub this dive opens.
- F7 · Deploy to Fly — the chapter this capstone proves live.