All projects
Personal project · self-hosted on ZimaOS

ZimaControl: monitoring and control panel for my NAS

2026Self-hostingTypeScriptFastifyReactSQLiteDockerTailscaleSelf-hosting

A single-container control panel for my ZimaOS home server: live hardware, drive and container monitoring with alerts, safe Docker Compose updates with automatic rollback, and one table that drives private Tailscale access to every app.

The problem

My NAS runs ZimaOS and a dozen self-hosted apps in Docker. The stock dashboard couldn't tell me what I actually cared about: whether a drive is overheating or reporting bad sectors, which containers are running outdated images, or whether a container that should be behind a VPN has silently fallen back to the open network. Wiring each app to a private tailscale serve HTTPS address was manual and easy to get wrong.

ZimaOS is also unusual to build for: the root filesystem is read-only and /var is wiped on reboot. So ZimaControl ships as one Docker container and keeps all of its state in a single data folder.

ZimaControl overview with an overheating-drive alert, CPU, memory, network and pool tiles, five drive bays and app tiles
The overview in demo mode: a drive at its temperature limit raises an alert, the bay view shows fill, temperature and health at a glance, and every app's container status sits below.

How it works

From sensor to notification

  1. 01
    Collect
    Six collectors read CPU, memory, storage, network and containers every 10 s; SMART every 60 s
  2. 02
    Store
    SQLite tiers: 10 s data for 1 hour, 1-minute averages for 7 days, hourly for a year
  3. 03
    Stream
    Each snapshot is pushed to the browser over a WebSocket; REST serves history
  4. 04
    Evaluate
    Alert rules run on every sample and every container discovery
  5. 05
    Notify
    ntfy, Gotify, Discord or email, only on open, escalate and resolve
  6. 06
    Act safely
    Updates, endpoints and firewall changes: plan, apply, verify, roll back
  • Monitoring. Collectors read the host's /proc and /sys through read-only mounts every 10 seconds: CPU per core with temperatures and load, memory, filesystems, disk throughput and IOPS, network per interface, and CPU, memory and network per container. Drive health comes from smartctl every 60 seconds, using standby mode so sleeping disks aren't woken.
  • Storage that doesn't grow forever. Samples go into SQLite in three tiers: 10-second data for an hour, 1-minute averages for a week and hourly averages for a year.
  • Alerts. 13 rule types, including drive temperature, SMART failures and reallocated sectors, full filesystems, container restart loops, a VPN-leak check and updates for internet-exposed apps. Notifications go to ntfy, Gotify, Discord or email, and only when an alert opens, escalates or resolves.
  • Safe updates. Registry digest checks show which images have updates, and catch digest-pinned images that can never update. Updates can be manual, notify-only or automatic on a schedule.
  • Endpoints. One table generates the tailscale serve commands, dashboard tiles and app URL settings, checks for conflicts and shows a diff before applying.
ZimaControl updates page listing containers with current and available versions and apply buttons
Updates: available versions per container, per-stack policies, and a warning for digest-pinned images that will never update even though they look like they track a tag.

Built to be safe

Every risky action follows the same pattern: snapshot, apply, verify, and roll back automatically if verification fails. That covers compose edits, image updates, endpoint changes and firewall rules, and firewall changes also have a five-minute dead-man switch that survives restarts. Everything is written to an append-only audit log.

The panel is reachable only through Tailscale: the port is bound to localhost, the single admin account uses argon2id with rate-limited sign-in, and write requests must be JSON from the panel's own origin. A secrets scanner flags weak or placeholder passwords in compose files and can generate replacements.

Code by part (lines)

Server
8,811
Web interface
5,657
Tests (~148 cases)
1,298
TypeScript throughout; the server holds the collectors, alert engine, Docker and firewall logic.

Source: wc -l over server/src and web/src in the repository

Engineering

TypeScript end to end: a Fastify 5 server with dockerode and better-sqlite3, and a React 19 front end with live updates over a WebSocket. GitHub Actions typechecks, runs the test suite, builds a multi-stage Alpine image, smoke-tests it and publishes it to the GitHub Container Registry. A demo mode simulates the whole NAS, which is how the screenshots here were taken.

It is deliberately built for my own box (five drive bays, Intel temperature sensors, my apps), not as a general product.

← Back to all projects
ZimaControl: monitoring and control panel for my NAS — Nichita Roscin