Skip to content

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/healthz is 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:

  1. 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).
  2. 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

  1. 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.
  2. 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.yml parses, 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_dispatch proxy) that adds the Authorization header server-side. No such relay exists in this repository: until one does, the UptimeRobot alert fires by email only, and the repository_dispatch automation is best-effort on free tier.

  • Enable notifications for: Down and Up.

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.