A minimal project for systems monitoring, designed for cases where Grafana and Prometheus are overkill.
This is not a typical "Status Page" project. There are no alerts, incident histories, push notifications, or CRUD UIs for configuration.
Simply create a configuration file, start the service, and you're done.
- Docker (for containerized usage)
- .NET 10 SDK (for development and building)
- PowerShell (for build scripts)
- Invoke-Build (for build automation)
- GitVersion
# docker-compose.yaml
services:
deucalion:
user: root
container_name: deucalion
image: ghcr.io/fdcastel/deucalion:latest
ports:
- 80:8080
environment:
- DEUCALION__PAGETITLE=Deucalion status
volumes:
- ./example.yaml:/app/example.yaml # Rename or copy your configuration file as needed.
- ./data/:/storage/# example.yaml
defaults:
intervalWhenUp: 00:00:03 # Default check interval when the monitor is UP
monitors:
ping-example:
!ping
host: cloudflare.com
group: Cloudflare
tcp-example:
!tcp
host: cloudflare.com
port: 443
group: Cloudflare
dns-example:
!dns
host: google.com
recordType: A
resolver: 1.1.1.1:53
group: Google
http-example:
!http
url: https://google.com
expectedStatusCode: 200
expectedResponseBodyPattern: .*
ignoreCertificateErrors: true
group: GoogleMonitoring behavior is defined in a YAML configuration file (e.g., deucalion.yaml).
This optional section allows you to define default values that apply to all monitors, or to all monitors of a specific type, unless overridden in a monitor's configuration. Example:
defaults:
intervalWhenUp: 00:01:00 # Check interval when the monitor is UP
intervalWhenDown: 00:01:00 # Check interval when the monitor is DOWN
timeout: 00:00:05
warnTimeout: 00:00:01
http:
timeout: 00:00:10
warnTimeout: 00:00:02
expectedStatusCode: 202
ignoreCertificateErrors: true
dns:
recordType: AAAA
resolver: 8.8.8.8
ping:
timeout: 00:00:05
warnTimeout: 00:00:01You can set defaults for each monitor type as follows:
intervalWhenUp,intervalWhenDown,timeout,warnTimeout(global or for each monitor type)expectedStatusCode,expectedResponseBodyPattern,ignoreCertificateErrors,method(for http only)recordType,resolver(for dns only)
warnTimeout is optional. When neither a monitor-level value nor a defaults value is set, Deucalion derives one continuously from the monitor's recent response-time history:
- Auto-WARN =
P95 × 3, with a 5 ms floor and a per-monitor-type ceiling (1 s base, 500 ms fordnsandping). - The rolling window is the last 60 successful probes; until at least 20 samples are collected, the per-type ceiling is used as a sane fallback.
- An explicit
warnTimeout(in the monitor or in thedefaultsblock) always wins — auto only kicks in when both are unset.
The same threshold drives the sparkline scale in the UI: the chart's Y-axis is anchored at [0, WARN], so a steady probe reads as a flat line near the baseline and a slow one approaches the top.
A WARN probe means "up, but slow". It counts as available: it does not advance ignoreFailCount, and the monitor keeps polling at intervalWhenUp rather than dropping to intervalWhenDown.
This section defines the individual monitors. Each monitor has a unique name (e.g., ping-example) and a type indicated by a YAML tag (e.g., !ping).
The following optional parameters are available for all monitors:
group: A string to group monitors together in the UI.href: URL to link to when the monitor name is clicked.intervalWhenUp: Check interval when the monitor is UP (except forcheckin).intervalWhenDown: Check interval when the monitor is DOWN (except forcheckin).
You can use ${MONITOR_NAME} in monitor fields to insert the monitor's name dynamically. Example:
monitors:
google: !http
url: https://${MONITOR_NAME}.comThis will set the URL to https://google.com.
| Type | Required Fields | Optional Fields |
|---|---|---|
| ping | host |
timeout, warnTimeout, intervalWhenUp, intervalWhenDown, group, href |
| tcp | host, port |
timeout, warnTimeout, intervalWhenUp, intervalWhenDown, group, href |
| dns | host, recordType, resolver |
timeout, warnTimeout, intervalWhenUp, intervalWhenDown, group, href |
| http | url |
expectedStatusCode, expectedResponseBodyPattern, ignoreCertificateErrors, timeout, warnTimeout, intervalWhenUp, intervalWhenDown, group, href, method |
| checkin | (none) | secret, intervalToDown, group, href |
ping-example:
!ping
host: cloudflare.com # Required: The hostname or IP address to ping.tcp-example:
!tcp
host: cloudflare.com # Required: The hostname or IP address to connect to.
port: 443 # Required: The TCP port to connect to.dns-example:
!dns
host: google.com # Required: The hostname to query.
recordType: A # Required: The DNS record type (e.g., A, AAAA, MX, CNAME).
resolver: 1.1.1.1:53 # Required: The DNS resolver IP address and port.http-example:
!http
url: https://google.com # Required: The URL to request.
expectedStatusCode: 200 # (Optional) Expected HTTP status code. Defaults to 200-299.
expectedResponseBodyPattern: .* # (Optional) Regex pattern to match against the response body.
ignoreCertificateErrors: true # (Optional) Set to true to ignore SSL/TLS certificate errors. Defaults to false.
warnTimeout: 00:00:00.250 # (Optional) Time threshold after which the monitor shows a 'Warning' state. If omitted, derived from history. Format: HH:MM:SS.fff.
timeout: 00:00:02 # (Optional) Time after which the request is considered failed. Format: HH:MM:SS or HH:MM:SS.fff. Defaults to 00:00:05.A passive monitor that waits for an external system to report ("check in") over HTTP.
checkin-example:
!checkin
secret: your-secret-key # (Optional) If set, must be sent in the `deucalion-checkin-secret` header.
intervalToDown: 00:05:00 # (Optional) Time without a check-in before the monitor goes DOWN. Defaults to 00:01:00.Check in with a POST to /api/monitors/{monitorName}/checkin:
curl -X POST \
-H 'deucalion-checkin-secret: your-secret-key' \
http://localhost:5000/api/monitors/checkin-example/checkin- Only
POSTis accepted -- there is noGETform. - The secret travels in the
deucalion-checkin-secretheader, never in the URL. secretis optional. If you omit it no authentication is performed: anyone who can reach the endpoint can mark the monitor UP.- Each check-in marks the monitor UP. If none arrives within
intervalToDown, it goes DOWN.
- Configuration files over CRUD forms
- Hexagonal Architecture
- K.I.S.S.
- Do One Thing And Do It Well: Not a "Status Page" (with incidents, justifications, etc)
Deucalion.Core: Base types and events shared between servers and clients.Deucalion.Application: Core engine and configuration.Deucalion.Network: Base network monitors.Deucalion.Storage: Persistence and statistics.Deucalion.Api: Server-side ASP.NET Web API application.Deucalion.Service: Service Host forDeucalion.Api. Can run as a Windows Service.Deucalion.Tests: xUnit tests.deucalion-ui: Client-side SolidJS single-page application.
Open Deucalion.sln with Visual Studio 2022.
Start both Deucalion.Api and deucalion-ui projects. You may set multiple startup projects for this.
Do not use
Deucalion.Servicefor debugging. It uses a static (pre-built) version of the UI (you need to runInvoke-Build Buildfirst).
Run
Invoke-Build DevThis will start both Deucalion.Api and deucalion-ui projects in development mode. Any changes to source files will be detected and reloaded automatically.
The server publishes every monitor event over Server-Sent Events. To tail it from a terminal:
curl -N http://localhost:5000/api/monitors/eventsIn the Development environment, the log level for the Deucalion.Api namespace is set to Debug. This generates a log entry for each message received from EngineBackgroundService.
For Production environments, the log level is Information (the default). To change this, you can run the application with
--Logging:LogLevel:Deucalion=Debug
in the command line, or change the appropriate value in appsettings.json.
Install Invoke-Build.
Invoke-Build or Invoke-Build build will put all artifacts in the ./publish folder.
Invoke-Build test runs the .NET unit tests (dotnet test) and the frontend unit tests (Vitest).
End-to-end tests are separate -- they boot both servers themselves:
npm --prefix ./src/ts/deucalion-ui run test:e2eMIT.