Self-hosted · Zero dependencies

Uptime monitoring that's made for the wall.

UptimeZen continuously checks your servers and services and shows their status on a glanceable, dark dashboard built to be left running on a screen. One tiny container, no database to run, nothing to npm install.

  • HTTP · TCP · Ping checks
  • ~80 MB image
  • amd64 + arm64
0
Runtime dependencies
~80 MB
Docker image size
3
Check types
1
SQLite file to back up
Features

Everything a home lab needs — and nothing it doesn't

Designed to be the calmest screen in the house. Powerful where it counts, invisible where it shouldn't get in the way.

Three check types

HTTP/HTTPS with status-code, keyword and self-signed TLS handling, raw TCP port checks, and ICMP ping — all from one place.

Glanceable dashboard

Big status tiles, live heartbeat bars where bar height is response time, rolling uptime %, and automatic grouping you can read across a room.

Per-monitor detail

Click any card for a response-time graph over 24h / 7d / 30d, uptime per window, min/avg/max latency, and a full incident log.

Drag-and-drop arrange

Reorder cards and groups — and move cards between groups — directly on the dashboard. Your layout is saved automatically.

Built for wall displays

Fullscreen kiosk mode, an auto-hiding cursor, layouts that scale up on TVs and 4K, and live updates with no flicker.

Tiny & self-contained

A single ~80 MB container — no external services, no separate database container, nothing else to wire up.

One file to back up

Everything persists to a single SQLite file in a mounted volume. Copy it and you have a complete backup of monitors and history.

Runs anywhere

Multi-arch images for linux/amd64 and linux/arm64 — the same build runs on an x86 mini-PC or a Raspberry Pi.

Zero dependencies

Built entirely on Node.js built-ins (node:sqlite, node:http). Nothing to compile, no native builds, no supply chain to worry about.

Status-aware ambient

New

The whole background is part of the display: it glows a calm accent while everything is healthy, washes amber the moment something degrades, and turns red when a monitor goes down. You can read the state of your lab from across the room — before you can read a single number. Respects prefers-reduced-motion.

Instrument-style numerals

New

Response times, uptime percentages and the clock are set in Space Mono, a self-hosted monospace — readouts stay crisp and don't jitter as values tick over. It ships as a bundled font file, not an npm package, so the zero-dependency promise still holds.

Slack notifications

Get a Slack message the moment a monitor goes down — and another when it recovers, with the downtime duration. Alerts fire on state changes only, never on every check, so the channel stays quiet until it matters. Paste an incoming-webhook URL, hit Send test, and you're live — with a per-monitor toggle to mute noisy ones. Zero extra dependencies; it's a plain HTTPS POST.

Optional password protection

Open by default — perfect on a trusted LAN. Set a single environment variable, UPTIMEZEN_PASSWORD, to put a login in front of the dashboard and API: one shared password, a signed session cookie, and rate-limited sign-ins. Change it later right from the UI; no per-user accounts to manage.

A look inside

Calm by default, detailed on demand

See the whole lab at a glance, then click into any service for the full story.

The dashboardWall view · status tiles + heartbeat bars
Monitor detailResponse time over 24h / 7d / 30d + incident log
Manage panelAdd, edit, pause and test checks — no YAML
How it works

From docker run to wall display in minutes

01

Run one container

Start UptimeZen with docker run or compose, mounting a data folder. On first run it seeds a few example monitors so you see it working immediately.

02

Add your services

Open the gear icon and add HTTP, TCP or ping checks — intervals, timeouts, expected status, keyword match, retries and "degraded over X ms". No config files required.

03

Put it on a screen

Each monitor runs on its own timer and stores results as heartbeats. Hit fullscreen, point a Raspberry Pi or mini-PC at it, and leave it on the wall.

Under the hood

A single Node.js process serves the dashboard, a JSON API and the static UI. A per-monitor scheduler runs self-rescheduling timers; the checker handles HTTP, TCP and ping probes with retries and redirects; results are written to SQLite and pruned on a retention window.

src/
├── server.js     HTTP server · JSON API · static files
├── db.js         node:sqlite schema + queries
├── checker.js    HTTP / TCP / ping probes (retry + redirect)
└── scheduler.js  per-monitor timers + history pruning
public/
├── index.html    dashboard + manage drawer
├── styles.css    dark, wall-display theme
└── app.js        live polling + manage UI
Quick start

Up and running in one command

Then open http://localhost:3000 (or http://<server-ip>:3000). Your data lives in the mounted ./data volume, so it survives restarts, rebuilds and upgrades.

bash
docker run -d \
  --name uptimezen \
  --restart unless-stopped \
  -p 3000:3000 \
  -v "$(pwd)/data:/app/data" \
  -e TZ=Europe/Stockholm \
  wahlstedt/uptimezen:latest

Then update any time

bash
docker compose pull
docker compose up -d

Avoid docker compose down -v — the -v would delete your data volume. Everything else leaves ./data untouched.

Configuration

Sensible defaults, easy to tune

Environment variables

VariableDefaultDescription
PORT3000HTTP port inside the container
TZUTCTimezone for displayed times (e.g. Europe/Stockholm)
DB_PATH/app/data/uptimezen.dbSQLite database location
RETENTION_DAYS30How long heartbeat history is kept
UPTIMEZEN_PASSWORD(unset)Set to require a login. Unset = open (no auth).
UPTIMEZEN_SESSION_DAYS30How long a login session stays valid

Volumes

PathRolePurpose
/app/dataRequiredHolds the SQLite database — monitors and history.
/app/configOptional · read-onlyDrop a monitors.json here to bulk-import on first run.

A built-in HEALTHCHECK hits /api/health, so docker ps and orchestrators report container health out of the box.

Security

UptimeZen is open by default — ideal on a trusted LAN. Set UPTIMEZEN_PASSWORD to put a login in front of the dashboard and API: a single shared password with a signed, rate-limited session that you can change later from Configuration → Change password. Even with a password set, don't expose port 3000 directly to the internet without TLS — keep it behind a reverse proxy or a VPN.

Give your home lab a screen worth looking at.

One tiny container. Zero dependencies. Your whole stack at a glance.