How to Monitor Bakeware (Elixir) with Vigilmon
Bakeware is an Elixir library that compresses Mix releases into single self-contained executable binaries. The resulting file bundles the Erlang runtime, BEAM bytecode, and all dependencies — no Erlang or Elixir installation required on the target machine. Bakeware binaries boot in under a second and can be deployed as sidecar CLIs, microservices, or background daemons anywhere a Linux or macOS binary can run.
But the deployment simplicity that makes Bakeware binaries attractive also creates new operational blind spots. A binary that crashes silently on a server produces no immediate alert. A distributed CLI run across a fleet may fail on some nodes but not others. An HTTP microservice packed into a Bakeware binary still needs external verification that it is accepting requests after deployment. These failures demand active monitoring.
This tutorial adds production observability to an Elixir application distributed as a Bakeware binary:
- An HTTP health check embedded in the binary's server component
- HTTP uptime monitoring with Vigilmon
- Process liveness heartbeat for long-running binary processes
- Deployment verification heartbeat confirming the binary started successfully
- Alerts on downtime and missed heartbeats
Step 1: Add Bakeware to your project
# mix.exs
defp deps do
[
{:bakeware, "~> 0.2", runtime: false},
{:plug_cowboy, "~> 2.0"},
{:plug, "~> 1.14"},
{:jason, "~> 1.4"},
{:req, "~> 0.4"}
]
end
Configure the release to use Bakeware:
# mix.exs
def project do
[
app: :my_app,
version: "1.0.0",
releases: [
my_app: [
steps: [:assemble, &Bakeware.assemble/1],
bakeware: [compression_level: 19]
]
]
]
end
Step 2: Build and run the binary
# Build the binary
MIX_ENV=prod mix release
# The binary is produced at:
# _build/prod/rel/bakeware/my_app
# Run it
./_build/prod/rel/bakeware/my_app start
For HTTP service binaries, the application starts its Cowboy/Bandit server exactly as it would from a standard Mix release.
Step 3: Add a health check endpoint to your binary's HTTP server
If your Bakeware binary runs an HTTP service, add a health plug:
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = %{
vm: check_vm(),
uptime: uptime_seconds()
}
status = if checks.vm == :ok, do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(%{
status: (if status == 200, do: "ok", else: "degraded"),
checks: checks,
version: Application.spec(:my_app, :vsn) |> to_string(),
node: to_string(node())
}))
|> halt()
end
def call(conn, _opts), do: conn
defp check_vm do
# Verify the BEAM VM is responsive by running a trivial call
_ = :erlang.system_info(:process_count)
:ok
rescue
_ -> :error
end
defp uptime_seconds do
{uptime_ms, _} = :erlang.statistics(:wall_clock)
div(uptime_ms, 1000)
end
end
Mount it in your endpoint:
# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router
end
Test it after running the binary:
curl http://localhost:4000/health
# {"status":"ok","checks":{"vm":"ok","uptime":42},"version":"1.0.0","node":"my_app@hostname"}
Step 4: Send a startup heartbeat on binary launch
For any Bakeware binary — HTTP service or daemon — ping a Vigilmon heartbeat monitor as soon as the application is fully initialized:
# lib/my_app/startup_reporter.ex
defmodule MyApp.StartupReporter do
use GenServer
require Logger
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
@impl true
def init(state) do
# Report after all children are ready (defer to let supervisor finish)
send(self(), :report_startup)
{:ok, state}
end
@impl true
def handle_info(:report_startup, state) do
version = Application.spec(:my_app, :vsn) |> to_string()
Logger.info("Bakeware binary started: version=#{version} node=#{node()}")
ping_startup_heartbeat(version)
{:noreply, state}
end
defp ping_startup_heartbeat(version) do
url = Application.get_env(:my_app, :vigilmon)[:startup_heartbeat_url]
if url do
case Req.get(url, receive_timeout: 10_000) do
{:ok, _} ->
Logger.info("Startup heartbeat sent to Vigilmon")
{:error, reason} ->
Logger.warning("Startup heartbeat failed: #{inspect(reason)}")
end
end
end
end
Add MyApp.StartupReporter as the last child in your supervision tree so it fires only after all services are ready:
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
MyApp.StartupReporter # ← last in the list
]
Step 5: Set up HTTP monitoring in Vigilmon
Point Vigilmon at the health endpoint your Bakeware binary serves:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute (paid) or 5 minutes (free)
- Under Advanced, add a keyword check for
"ok"to verify the health status - Save
Why external monitoring matters for Bakeware binaries:
Bakeware binaries eliminate runtime dependencies, but they introduce their own failure modes. The binary may crash on boot due to a missing environment variable. A new version may fail to bind to the expected port. A daemon binary may silently exit after a transient error. None of these produce user-visible errors without external monitoring.
Vigilmon's response time history is also useful for detecting post-deployment regressions: a new binary version that takes longer to respond may indicate a startup issue or a performance regression in the new release.
Step 6: Heartbeat monitoring for long-running daemon binaries
For Bakeware binaries that run as background daemons (not HTTP services), use a heartbeat monitor instead:
# lib/my_app/workers/liveness_worker.ex
defmodule MyApp.Workers.LivenessWorker do
use GenServer
require Logger
@interval :timer.minutes(5)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
@impl true
def init(state) do
schedule_tick()
{:ok, state}
end
@impl true
def handle_info(:ping, state) do
ping_heartbeat()
schedule_tick()
{:noreply, state}
end
defp schedule_tick, do: Process.send_after(self(), :ping, @interval)
defp ping_heartbeat do
url = Application.get_env(:my_app, :vigilmon)[:liveness_heartbeat_url]
if url do
case Req.get(url, receive_timeout: 10_000) do
{:ok, _} -> :ok
{:error, reason} -> Logger.warning("Liveness heartbeat failed: #{inspect(reason)}")
end
end
end
end
In Vigilmon, create a Heartbeat monitor:
- New Monitor → Heartbeat
- Set interval to 7 minutes (5-minute ping interval with 2-minute grace)
- Copy the ping URL and set it as
VIGILMON_LIVENESS_HEARTBEAT_URLon the server
If the binary crashes or the daemon exits, Vigilmon will alert you within one check interval.
Step 7: Alerts via Slack
In Vigilmon, go to Notifications → New Channel → Slack and paste your incoming webhook URL.
Enable the channel on all monitors. Binary-related incidents commonly appear as:
- HTTP health check down (binary crashed or failed to bind to port)
- Response time spike (new binary version with a performance regression)
- Startup heartbeat missed (deployment failed — binary didn't reach full initialization)
- Liveness heartbeat missed (daemon exited silently between restarts)
Configure Vigilmon to send all alert types to your deployment channel so every binary lifecycle event is captured.
Step 8: Status page
- Status Pages → New Status Page in Vigilmon
- Add your HTTP health monitor and all heartbeat monitors
- Link the status page from your deployment runbook
README badge:

What you've built
| What | How |
|------|-----|
| Health check endpoint | HealthCheck plug with VM liveness + uptime |
| Version in health response | Application.spec/2 included in JSON payload |
| HTTP uptime monitoring | Vigilmon HTTP monitor → /health |
| Startup heartbeat | StartupReporter GenServer pings Vigilmon on init |
| Daemon liveness heartbeat | LivenessWorker pings Vigilmon every 5 minutes |
| Post-deploy verification | Vigilmon startup heartbeat miss = failed deployment |
| Slack downtime alerts | Vigilmon Slack notification channel |
| Status page | Vigilmon public status page |
Bakeware makes Elixir applications trivial to distribute. Vigilmon ensures those distributed binaries stay alive and healthy wherever they run.
Next steps
- Store the binary version in the heartbeat URL as a query parameter (
?version=1.0.0) so Vigilmon logs confirm which version is running - Run a second Vigilmon HTTP monitor on a canary server to catch regressions before rolling out a new binary to the full fleet
- Use Vigilmon response time history to compare startup latency across binary versions
- Create a dedicated Vigilmon project per binary to isolate alert noise between services
Get started free at vigilmon.online.