Runbook — Uptime monitoring
Beta posture (Sprint 34.5, issue #568): during the restricted beta we run only the external UptimeRobot monitor on
/api/healthz. The self-hosted Uptime Kuma stack (.docker/uptime-kuma/) is not deployed — its files stay in the repository for reversibility but no container runs on the VM. The single off-host probe on/api/healthzis enough to detect a host-level outage and raise an incident. Re-enable Uptime Kuma later by deploying the stack and restoring the "Self-hosted" layer below. See ADR-039 for this posture and the parallel Sentry-SaaS one.
Target (post-beta) two-layer uptime monitoring for Bike Trip Planner production:
- Self-hosted (Uptime Kuma) on the prod VM — sub-minute detection,
rich monitor types, public status page. Configuration documented in
.docker/uptime-kuma/README.md. Not deployed in beta (see banner above). - External (UptimeRobot) — single off-host probe that survives a full VM outage. This is the only active layer in beta. This document covers (2).
Why two layers?
Uptime Kuma runs on the same Oracle Cloud Always Free VM as the application (see ADR-019). If the VM goes down — kernel panic, network ACL change, Oracle reclaims the instance — Uptime Kuma goes down with it and emits no alert. UptimeRobot's free tier runs from external probes; a single HTTP monitor is enough to detect a host-level outage and trigger the incident workflow.
UptimeRobot setup
Account
- Create a free account on https://uptimerobot.com with the team alias
oncall@bike-trip-planner.com(or any shared inbox). Free tier allows 50 monitors at 5-minute intervals; we use one. - Enable two-factor authentication on the account.
Monitor
+ Add New Monitor:
- Monitor Type:
HTTP(s) - Friendly Name:
biketrip-healthz - URL:
https://www.bike-trip-planner.com/api/healthz - Monitoring Interval:
5 minutes(free tier minimum) - Monitor Timeout:
30 seconds - HTTP Method:
GET - Accepted Status Codes:
200 - Alert Contacts: see below
Alert contact — GitHub repository_dispatch webhook
My Settings → Alert Contacts → Add Alert Contact:
- Alert Contact Type:
Webhook - Friendly Name:
github-incident-dispatch - URL to Notify:
https://api.github.com/repos/vincentchalamon/bike-trip-planner/dispatches - POST Value (JSON Format): enable
- POST Value (the fields
incident-create.ymlparses, see incident-alerting.md):
{
"event_type": "uptime_alert",
"client_payload": {
"monitor_name": "*monitorFriendlyName*",
"monitor_url": "*monitorURL*",
"status": "*alertTypeFriendlyName*",
"message": "*alertDetails*",
"responseTime": *responseTime*
}
}
- Custom HTTP Headers (UptimeRobot Pro feature; on free tier, encode the token in the URL query string as a fallback — see workaround below):
{
"Authorization": "Bearer <INCIDENT_DISPATCH_TOKEN>",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
}
Free tier workaround: UptimeRobot free does not allow custom headers. Route the webhook through a tiny relay (Cloudflare Worker or a GitHub-hosted
workflow_dispatchproxy) that adds theAuthorizationheader server-side. No such relay exists in this repository: until one does, the UptimeRobot alert fires by email only, and therepository_dispatchautomation is best-effort on free tier.
- Enable notifications for:
DownandUp.
Attach the alert contact to the biketrip-healthz monitor.
Verification
# Simulate a down event from a workstation (requires the PAT):
curl -fsS -X POST \
-H "Authorization: Bearer $INCIDENT_DISPATCH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/vincentchalamon/bike-trip-planner/dispatches \
-d '{"event_type":"uptime_alert","client_payload":{"monitor_name":"biketrip-healthz","monitor_url":"https://www.bike-trip-planner.com/api/healthz","status":"down"}}'
GitHub answers 204 No Content on success, and incident-create.yml opens (or
comments on) a P1 issue within about 30 seconds.
Severity & escalation
| Monitor | Source | Severity | Deployed in beta |
|---|---|---|---|
/api/healthz (5 min, external) |
UptimeRobot | P1 | Yes |
Post-deploy smoke test (deploy.yml) |
GitHub Actions | P2 | Yes |
/api/healthz (60 s) |
Uptime Kuma | P1 | No |
/api/health (5 min) |
Uptime Kuma | P2 | No |
Keyword / (monitor named homepage) |
Uptime Kuma | P1 | No |
Severities are computed by incident-create.yml (see
severity-levels.md). Post-beta, UptimeRobot stays
redundant with the Uptime Kuma /api/healthz monitor: if both fire, the issue
is real; if only UptimeRobot fires, the VM (or Uptime Kuma) is the problem.
Token rotation
INCIDENT_DISPATCH_TOKEN is a fine-grained GitHub PAT with
Contents: Read and write on vincentchalamon/bike-trip-planner only.
- Lifetime: 90 days.
- Storage: GitHub Actions secret + the UptimeRobot alert contact. It is not an app runtime secret and is not in Ansible Vault.
- Rotation: see
incident-alerting.md ("Rotating
INCIDENT_DISPATCH_TOKEN").
Never commit the token to the repository.