Health Checks

Potions decides your app is up by requesting one path on it, the health check path. It defaults to /.

The path is used in two places:

  • Zero downtime deploys. Potions requests the health check path during deployment. If it never passes, the new instance is stopped and your current release keeps serving.
  • Uptime monitoring. If enabled, Potions requests your health check path and records the result on the app's Health Checks tab. Available on the Solo plan and up, with a verified primary domain.

Any 2xx or 3xx response passes. Redirects are not followed, and the request is a plain GET with no cookies or login session.

Setting the Path

  • When adding an app: open Advanced options and set Health check path.
  • Later: the app's Settings tab, Health check section. The change applies to the next deploy and the next uptime check.

The path is case-sensitive and can contain letters, numbers, dots, hyphens, underscores, tildes and slashes. Query strings aren't supported.

Adding a /health Route

/ works out of the box, but every check then renders your home page, and if / requires login the check is really testing your login redirect. A dedicated route is lighter and can't be broken by either. Phoenix doesn't generate one, so add a small plug and make it the first plug in your endpoint:

# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
  import Plug.Conn

  def init(opts), do: String.split(Keyword.get(opts, :path, "/health"), "/", trim: true)

  def call(%Plug.Conn{method: "GET", path_info: segments} = conn, segments) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "ok")
    |> halt()
  end

  def call(conn, _segments), do: conn
end
# lib/my_app_web/endpoint.ex, right after `use Phoenix.Endpoint`
plug MyAppWeb.Plugs.HealthCheck, path: "/health"

Placed first, it answers before static files, sessions and request logging, so the check never shows up in your logs. Then set the health check path to /health.

If you want to ensure your database is reachable, you can run a simple query like Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1") from the call/2 function.

When a Check Fails

See Troubleshooting Failed Deploys for what each status means.