1. Documentation
  2. Deploying
  3. Self-hosting
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

Self-hosting

Run on Bun or Node with your own scheduler, SQLite or PostgreSQL.

A self-hosted status page is an ordinary framework server plus a scheduler. Pick the database your host can actually keep — SQLite on a persistent disk, PostgreSQL anywhere else — and register the three cron jobs.

bash
bun create @heyo-sh/heyo-status my-status-page \  --template react-router --deployment later --database sqlite

--deployment later skips the platform overlay and leaves a project that runs anywhere.

Scheduling with node-cron

scheduler.ts
import { scheduleStatusPage } from "@heyo-sh/heyo-status/deployments/node";import cron from "node-cron";import { statusPage } from "./heyo-status.config";const scheduler = scheduleStatusPage(statusPage, cron);process.once("SIGTERM", () => scheduler.stop());process.once("SIGINT", () => scheduler.stop());

scheduleStatusPage() registers runChecks, summarize and cleanup with their configured expressions and returns { stop }. Failures inside a job are contained, so one bad run never kills the process. Wrap the calls yourself if you want them reported to your own observability.

The scheduler argument is structural — anything exposing schedule(expression, task) and returning { stop } works, including a test double.

Running inside the server process

server.ts
import { scheduleStatusPage } from "@heyo-sh/heyo-status/deployments/node";import cron from "node-cron";import { statusPage } from "./heyo-status.config";scheduleStatusPage(statusPage, cron);Bun.serve({  port: Number(process.env.PORT ?? 3000),  fetch: (request) => statusPage.handler(request),});
One scheduler per deployment

If you run several replicas behind a load balancer, only one should schedule. Otherwise every replica checks every monitor, multiplying outbound traffic and writing duplicate history rows.

Options: a dedicated scheduler container, a leader election, or an external trigger hitting POST /admin/checks/run on a timer.

External triggers

Any scheduler that can make an authenticated HTTP request works:

bash
# crontab*/5 * * * * curl -fsS -X POST https://status.acme.com/api/heyo-status/admin/checks/run \  -H "authorization: Bearer $HEYO_STATUS_API_TOKEN" > /dev/null
Kubernetes CronJob
apiVersion: batch/v1kind: CronJobmetadata:  name: heyo-status-checksspec:  schedule: "*/5 * * * *"  jobTemplate:    spec:      template:        spec:          restartPolicy: OnFailure          containers:            - name: curl              image: curlimages/curl:latest              args:                - -fsS                - -XPOST                - -H                - "authorization: Bearer $(TOKEN)"                - https://status.acme.com/api/heyo-status/admin/checks/run              env:                - name: TOKEN                  valueFrom:                    secretKeyRef: { name: heyo-status, key: api-token }

Note that /admin/checks/run only runs checks. Summarize and cleanup have no HTTP route; run them from the CLI or with scheduleStatusPage.

Without a framework

The CLI can serve the API on its own, which is enough for a JSON-only status service or a smoke test:

bash
heyo-status dev --port 4000

It serves every endpoint under basePath and a small index page at /. The full UI needs a framework project.

A container

Dockerfile
FROM oven/bun:1.4.3WORKDIR /appCOPY package.json bun.lock ./RUN bun install --frozen-lockfileCOPY . .RUN bun run buildENV HEYO_STATUS_DB_PATH=/data/status.sqliteVOLUME /dataEXPOSE 3000CMD ["bun", "run", "start"]

Mount /data on a persistent volume — SQLite on a container-local filesystem loses the history on every redeploy.

Checklist

ItemWhy
HEYO_STATUS_API_TOKEN setOtherwise the management API stays closed.
siteUrl setFeeds, notifications and subscription links need a canonical URL.
Persistent database storageSQLite on an ephemeral disk loses everything on restart.
Exactly one schedulerAvoids duplicate checks and duplicate alerts.
Node.js 20.19+ (22.12+ for Astro 7)Required for tcp and ssl monitors.
heyo-status db migrate on releaseFor PostgreSQL; SQLite migrates itself.
Reverse proxy forwards the real hostOr siteUrl will disagree with the request origin.

Related

  • SQLite
  • CLI commands
  • Security checklist
Vercel< PreviousAPI overviewNext >

Powered by heyo

On this page

Scheduling with node-cronRunning inside the server processExternal triggersWithout a frameworkA containerChecklist