- Web
- DevOps
Infra Sentinel
Self-hosted monitoring for websites, APIs and servers. It opens incidents by itself, alerts the right person by email, chat or webhook, and publishes status pages, all from one PostgreSQL database.
- Personal project, open source (MIT)
- 151 API endpoints, 43 tables, 21 pages
- 444 unit, 54 end-to-end and 25 browser tests passing
- TypeScript, NestJS, Next.js, React, PostgreSQL, Go, Tailwind CSS, Playwright

The problem
When a service stops working, someone has to notice, decide who should act, and tell the customers. Infra Sentinel does those three jobs from one place that you host yourself.
It is meant for people and small teams who host their own services and want monitoring, incidents and status pages without a paid service, and for developers who want to read or reuse the code.
The approach
A web console, a REST API, a Go agent and a Go command line tool share one PostgreSQL database. The Next.js web app serves the console and forwards /api/v1 and /status to the NestJS API, which runs the checks, the alert engine and the workers in the same process.
PostgreSQL also holds the job queues, locks and hourly rollups, so there is one thing to install and back up, and no Redis or message broker. Every dependency is open source, and nothing needs a paid service or an API key.
What it does
- HTTP(S), keyword, TCP, ping, DNS, SSL certificate and heartbeat monitors
- Assertions on the status code, on text and on a JSON path, and a state change only after a set number of failures in a row
- Incidents opened and closed by an alert engine, with timelines, notes and postmortems
- Alerts by email, Slack, Discord, Telegram, ntfy, Gotify or a signed webhook
- Escalation policies, on-call schedules, silences and maintenance windows
- Public status pages rendered as plain HTML, with an RSS feed and email subscribers
- SLA reports as a page, a PDF or a CSV
- A Go agent that reports CPU, memory, disks, network, containers and services
- A live console fed by server-sent events
- Four workspace roles, scoped API keys, signed webhooks and an audit log with CSV export
- Two-factor sign-in with an authenticator app, and single sign-on with OpenID Connect
- English, French and Arabic, with a right-to-left layout
- A command line tool, sentinel, written in Go
Where the engineering is
Queues without Redis
The check scheduler wakes up every second, claims the monitors that are due and runs up to 20 checks at a time. Notifications are jobs in PostgreSQL too: a worker claims a job, sends it and tries again up to three times if it fails.
Jobs are claimed with SELECT ... FOR UPDATE SKIP LOCKED inside a transaction, so a second API process cannot take the same job, and a crash loses nothing because the claim never committed.
Incidents that are never lost or doubled
Every two seconds the alert engine looks for monitors that are down with no open incident, opens one and queues a notification for each channel. When the monitor recovers, it resolves the incident and sends the recovery message.
Because it compares the state of monitors with the open incidents, instead of reacting to single events, a restart or a second instance does not change the result.
, Open the full-size image (opens in a new tab)A live console without WebSockets
Server-sent events deliver changes to the console, and TanStack Query invalidates what changed. When the stream drops, the console falls back to refreshing every 30 seconds.
, Open the full-size image (opens in a new tab)Secure by default
Passwords are hashed with scrypt. Sessions are stored as hashes and sent in an HttpOnly cookie, so signing out ends the session at once. Values the API must read back, such as two-factor seeds and webhook secrets, are encrypted with AES-256-GCM.
Sign-in and API calls are rate limited, an option stops monitors from probing private addresses, and the web app sets a Content-Security-Policy and other security headers. Migrations are plain SQL, applied at start-up under a PostgreSQL advisory lock, so several instances can start together.
Right-to-left Arabic
The language is chosen on the server from a cookie or the browser setting, so pages arrive already translated, and Arabic mirrors the whole layout.
, Open the full-size image (opens in a new tab)One command checks everything
There is no CI service, on purpose. One script, bash scripts/verify.sh, runs every check and prints one table: syntax, documentation links, builds, unit and end-to-end tests against a real PostgreSQL, the Go agent and command line, browser tests with Playwright, a backup and restore check, and the production images.
, Open the full-size image (opens in a new tab)Screens
, Open the full-size image (opens in a new tab)Uptime for several periods, a response time chart and 90 days of history.
, Open the full-size image (opens in a new tab)One card per machine, filled by the Go agent.
, Open the full-size image (opens in a new tab)Charts from one hour to seven days for one machine.
, Open the full-size image (opens in a new tab)Pick the monitors and servers to show, set the look, publish.
, Open the full-size image (opens in a new tab)Availability against a target, as a page, a PDF or a CSV.
, Open the full-size image (opens in a new tab)Who changed what, with a CSV export.
, Open the full-size image (opens in a new tab)The Go command line tool lists its commands.
, Open the full-size image (opens in a new tab)The console in the dark theme.
How it's built
| Layer | Technology |
|---|---|
| API | NestJS 11 and TypeScript 5.7, zod request schemas, plain SQL with node-postgres, generated Swagger documentation |
| Web | Next.js 16, React 19, Tailwind CSS 4 with light and dark themes, TanStack Query 5 |
| Agent and CLI | Go 1.22 with the standard library only, one static binary per platform |
| Database | PostgreSQL 17 for data, job queues, locks and hourly rollups, with 7 SQL migrations |
| Testing | Jest unit and end-to-end tests, Playwright browser tests and Go tests |
| Operations | A Docker production setup, and backup and restore scripts with a restore check |
, Open the full-size image (opens in a new tab)Results
- 68,750 lines of code and tests, counted from the repository.
- 151 API endpoints in 17 controllers, and 21 web pages, 14 of them behind sign-in.
- 43 database tables in 7 migrations.
- 7,902 lines of Go with no external modules.
- 444 API unit tests, 54 end-to-end tests and 25 browser tests passed in the recorded verification run.
- 3 languages in the console and 16 documentation pages.
What I would build next
- Monitor groups, and checks from several regions instead of only the machine that hosts the API.
- Domain expiry checks, flapping detection and p50 response times.
- Maintenance windows in a chosen time zone, with end dates.
- On-call layers and overrides, and a message to the assignee when an incident is assigned.
- SMS, phone, Microsoft Teams and PagerDuty channels; the signed webhook covers custom integrations for now.
- Stored container history, a fuller macOS agent and a Windows agent.
Built with
- TypeScript
- NestJS
- Next.js
- React
- PostgreSQL
- Go
- Tailwind CSS
- Playwright