Live Linux host and Docker Compose monitoring, built with Rust and Alpine.js.
  • Rust 35.6%
  • JavaScript 21.5%
  • CSS 21.2%
  • HTML 13.2%
  • Python 8.2%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-16 10:47:48 +02:00
config Configure public visibility and add synthetic admin screenshots 2026-10-10 18:08:16 +02:00
docs Redesign dashboard with Cal styling and saved themes 2026-10-11 12:40:02 +02:00
src Keep container and project logs streaming across restarts 2026-10-16 10:47:48 +02:00
static Explain limitations of the public dashboard 2026-10-11 16:20:36 +02:00
tests Keep container and project logs streaming across restarts 2026-10-16 10:47:48 +02:00
.dockerignore Initial release of Hostwatch 2026-10-10 13:32:44 +02:00
.gitignore Initial release of Hostwatch 2026-10-10 13:32:44 +02:00
Cargo.lock Add admin Compose project editing and deployment controls 2026-10-10 19:15:46 +02:00
Cargo.toml Add admin Compose project editing and deployment controls 2026-10-10 19:15:46 +02:00
compose.yml Add admin Compose project editing and deployment controls 2026-10-10 19:15:46 +02:00
DESIGN.md Redesign dashboard with Cal styling and saved themes 2026-10-11 12:40:02 +02:00
Dockerfile Add admin Compose project editing and deployment controls 2026-10-10 19:15:46 +02:00
LICENSE Initial release of Hostwatch 2026-10-10 13:32:44 +02:00
NOTICE Redesign dashboard with Cal styling and saved themes 2026-10-11 12:40:02 +02:00
README.md Keep container and project logs streaming across restarts 2026-10-16 10:47:48 +02:00

Hostwatch

A responsive, live Linux host and Docker Compose monitor. Built with Rust, Axum, Alpine.js, and an Alpine Linux runtime. No frontend build step, external CDN, or metrics database is required.

The frontend follows DESIGN.md, with saved light/dark themes, Cal Sans headings, Inter UI text, compact tables, and a sidebar with expandable Compose projects. Fonts are served locally under their SIL Open Font Licenses (Cal Sans, Inter).

Screenshots

Full administrator interface with entirely synthetic demo data; no real container names, host details, or logs appear in these images.

Administrator overview with host metrics, Compose summary, network charts, and UPS

More administrator screenshots: Containers, Temperatures, Hardware, UPS, Processes, Storage, Network, and Logs.

To reproduce the screenshots, run python3 docs/readme-demo.py and open http://127.0.0.1:3011. This loopback demo uses the real interface with synthetic fixtures; it does not collect host data or run Docker actions.

Features

  • Live CPU, memory, swap, processes, storage, disk I/O, and network graphs.
  • Temperatures grouped by device and sensor family, with separate fan and power readings.
  • Automatically detected UPS monitoring: power status, battery charge, runtime, load, and voltage.
  • Collapsible Compose projects with container resource usage and state. Projects with no running services appear in a separate list with Start, Edit Compose, and Logs controls.
  • Independent start, stop, and restart controls for containers and whole projects.
  • Live container and combined project logs with ANSI colors, aligned sources, search, pause, and downloads.
  • Desktop and mobile layouts, saved light/dark themes, persistent page URLs, and immediate graph history on refresh.
  • CPU grids adapt to the available width and core count, with up to four columns and complete rows.

Run with Docker Compose and Relay

Hostwatch monitors a Linux Docker host. Install Docker Engine with Compose and set up Relay on the same host. Relay provides HTTPS and trusted member/administrator identity.

git clone https://git.sebastjan.org/sebastjan/hostwatch.git
cd hostwatch
docker compose up -d --build
docker compose ps

The supplied Compose configuration joins Relay's external reverse-proxy_default network. Register your production hostname in Relay with upstream http://system-monitor-production:3000. For a public dashboard, select Public with members (authRequired: false, adminOnly: false, and environmentSettings.production.publicWithMembers: true). Use unique aliases if deploying multiple instances. No host port is published.

The application listens on 0.0.0.0:3000. Anonymous visitors and non-admin Relay members receive the information enabled in config/public-access.json. The defaults show CPU, memory, uptime, load, process count, temperatures, disk/SSD names and throughput, UPS telemetry, aggregate network throughput, and basic Docker summaries (running counts and project CPU/RAM totals). Project names are hidden by default and displayed as “Project 1”, “Project 2”, etc. Both snapshot and SSE APIs use explicit field allowlists. Host identity, OS/kernel versions, container details, processes, filesystem paths, interface details, errors, and raw hardware paths remain private by default. Only a trusted X-Auth-Role: admin enables full metrics, logs, and lifecycle actions. This header must come from Relay, which authenticates users and strips client-supplied identity headers. The health endpoint is available inside the Docker network. Do not expose the service directly to untrusted clients.

The deployment mounts the host filesystem and sensors read-only and uses the host PID namespace. Docker socket access grants control over the host daemon, even with a read-only socket mount. Keep the host agent behind Relay even when its filtered dashboard is public. Relay's administrative socket is not needed or mounted.

Production serves packaged assets. The Compose configuration deploys only production. Changes require a rebuild:

docker compose up -d --build
docker compose logs --tail=50

The existing Compose project name, image, executable, and network aliases remain system-monitor for deployment compatibility. Hostwatch is the application and repository name.

Configure public visibility

Edit config/public-access.json before enabling public access in Relay. The Compose service mounts this file read-only. Reload after a config-only change with docker compose up -d --force-recreate production; rebuild for code changes. Outside Compose, set PUBLIC_ACCESS_CONFIG to the file path (default: config/public-access.json). A missing file, unknown option, or non-boolean value stops startup; omitted options are disabled.

These settings apply equally to anonymous visitors and non-admin members. Administrators retain the full view. The UI follows the server's enabled sections. Logs and lifecycle actions always require administrator access and cannot be enabled in this file.

Option Default Public information
host_metrics true CPU/core utilization, frequency, memory/swap, load, uptime, process count, chart history
host_identity false Hostname
software_versions false OS and kernel versions
hardware_names true CPU model and sensor device/model names, including SSD names; raw sysfs paths remain hidden
temperatures true Temperature, fan, and power readings
ups true UPS status and telemetry; driver paths and errors remain hidden
network_throughput true Aggregate network throughput and history
docker_overview true Running/total counts and anonymous project resource summaries
docker_project_names false Real project names in enabled Docker views
docker_details false Container names, IDs, images, services, states, and resource usage; controls are disabled
processes false Process names, PIDs, and resource usage
storage true Disk names and I/O throughput/counters
filesystem_details false Mount/device paths, filesystem types, and capacity/usage
network_details false Interface names and individual traffic/counters

docker_project_names alone exposes nothing; enable docker_overview or docker_details as well. Container details can contain a project's name in container names or image/service strings even when project labels are anonymized, so keep docker_details disabled to hide them. Relay can also remain members-only or admin-only; the visibility file does not change Relay's access mode.

UPS monitoring

When a UPS is detected, an overview card and a UPS navigation item appear automatically. The /ups page shows mains/battery status and warnings, battery charge, estimated runtime, load, and available voltage, power, and temperature readings. Unsupported readings display a dash. UPS data refreshes independently every five seconds; stale or failed queries display Unavailable without presenting old values as current.

Hostwatch automatically discovers local Network UPS Tools (NUT) driver sockets in standard runtime/state directories through the existing host mount. This supports models from any manufacturer supported by the installed NUT driver; there is no vendor or model whitelist. It only sends DUMPALL; it does not claim the USB device, start a competing driver, change UPS settings, or interfere with shutdown handling. Each query runs in a subprocess that automatically adopts the socket's group using the Compose SETGID capability. No host-specific group ID, UPS name, NUT network listener, or published port is required.

When no NUT socket is found, Hostwatch checks apcupsd status files (including a configured STATUSFILE), kernel power supplies explicitly identified as UPS, then standard USB HID UPS collection descriptors. Laptop batteries, keyboards, and other HID devices are excluded. USB HID devices without telemetry appear as detected with an explanation that a running NUT driver is needed. apcupsd status files older than 90 seconds are treated as unavailable.

Full readings require an existing compatible NUT/apcupsd driver or kernel UPS supply. Proprietary serial/network devices cannot be identified reliably without their driver configuration; Hostwatch does not probe arbitrary serial ports or scan networks. Unsupported readings display a dash rather than a fabricated value.

Install on your phone

Open your HTTPS Hostwatch hostname. Sign in through Relay for administrator access.

  • iPhone/iPad: in Safari, use Share → Add to Home Screen (enable Open as Web App when offered).
  • Android: in Chrome, use the menu → Install app or Add to Home screen.

Hostwatch opens in a standalone window with its own home screen icon. It requires a connection for live metrics, logs, and actions; an offline launch shows a reconnect screen. The service worker does not cache authenticated pages, metrics, logs, or actions. Logs and controls still require a Relay administrator session after installation.

Monitoring details

  • Overall and per-core CPU usage, frequency, model, load average, kernel, OS, uptime, process count.
  • Physical memory, available memory, and swap usage.
  • A dedicated Temperatures view groups CPU package, CPU cores, individual storage drives, graphics, network adapters, and motherboard readings. Devices retain distinct sysfs identities and drive model names where available. Hardware keeps fans and average power readings separate from temperatures. Sensor coverage depends on the host drivers.
  • Host filesystems, including ZFS datasets, capacity and available space. ZFS dataset capacities can share pool space.
  • Device and partition read/write throughput and lifetime byte counters. Partition and device counters overlap.
  • Physical, bridge, virtual, and loopback interface throughput and byte counters. Overview totals use physical interfaces when available to avoid counting the same traffic across virtual interfaces.
  • Top 100 processes by CPU, with PID, memory, status, and disk bytes since the previous sample. Process CPU can exceed 100% when using multiple cores.
  • All Docker containers, including stopped containers, grouped by com.docker.compose.project; unlabeled containers appear under Standalone.
  • Per-container CPU, memory, network/block I/O totals, PIDs, state, image, and Compose service. Container and Compose CPU percentages show a share of total host capacity, normalized by the number of logical CPUs; hovering reveals Docker’s original per-core percentage. Compose headers also show summed memory usage in bytes across all members, regardless of filters. The API retains Docker’s per-core cpu and adds memoryBytes (cache-adjusted usage parsed from Docker CLI stats). Usage metrics are unavailable for stopped containers. Docker network/block values are cumulative totals.
  • Live stdout/stderr logs with timestamps, ANSI colors (standard, bright, 256-color, and RGB), bold/italic/underline styling, initial 200-line tail, search, pause, and plain-text download. Compose logs merge all existing project containers, label each source, and keep ANSI state separate for each stream. Incoming output is processed in bounded batches and rendered with reused DOM rows to keep initial log bursts responsive. Terminal control codes are stripped and log text is HTML-escaped. Each container log stream automatically reattaches after restart or stop/start, resuming at its last timestamp without replaying delivered lines. This also applies to individual members of a combined project stream. The browser retains at most 1,500 lines; pausing skips incoming lines.

Host metrics and Docker stats are collected centrally per service and sent to browsers through SSE. Host metrics update every second, independently of Docker inventory and stats collection. The server retains the last 90 real samples and includes them in every metrics response so graphs render immediately on refresh, and connections retry automatically. There is no durable historical metrics database.

The interface groups related information into a small number of cards, with one accordion list for Compose projects. Projects start collapsed and expand when their header is clicked. Header controls reserve a Start slot so arrow, CPU, and Logs positions stay aligned. Logs and lifecycle controls are available directly in the header; Start appears only when at least one project container is not running. Logs follow incoming output only while at the bottom; scrolling up holds the current position, and Latest resumes following. Log timestamps display UTC hours, minutes, and seconds; hovering reveals full precision, and downloads preserve complete timestamps. Live readings use tabular digits and fixed table widths. Containers and processes default to name order. Container sorting also orders Compose projects by their aggregate CPU or memory percentage across all members, including members hidden by filters; CPU/memory sorting retains the selected order until Refresh order is clicked, so live updates do not shuffle rows.

Container and Compose controls support start, stop, and restart. Compose actions resolve all existing containers by the exact project label, regardless of browser search or state filters; they do not recreate services or remove containers or volumes. Actions run independently for unrelated containers and projects. Pending state and errors appear on the affected target; only overlapping controls are disabled. Action endpoints require administrator access, JSON, and a custom request header. The host root and sensor mounts are read-only. Access to the Docker Unix socket still grants powerful daemon capabilities despite the read-only mount; this service is a trusted host agent and must remain behind Relay's trusted identity handling. The Relay administration socket is never mounted into this application.

Development and checks

The restart regression check uses disposable Docker containers against a trusted private upstream: MONITOR_UPSTREAM_URL=http://CONTAINER_IP:3000 python3 tests/log-restart.py. It keeps single-container and combined project streams open across repeated restarts and stop/start, checks stdout/stderr and duplicate prevention, and opens Logs during a restart. Do not point this check at public ingress; it supplies the trusted administrator header directly.

Rust 2024, Node.js (for frontend tests), and Docker Compose are required. The vendored Alpine.js runtime means there is no npm install step.

cargo fmt --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
node --check static/app.js
node tests/ansi.test.js
node tests/ui.test.js
node tests/log-follow.test.js
node tests/actions-ui.test.js
node tests/compose-ui.test.js
node tests/pwa.test.js
node tests/reconnect.test.js
node tests/theme.test.js
node tests/cpu-layout.test.js
docker compose config --quiet

The deployment integration scripts (tests/smoke.py, tests/actions.py, and tests/project-logs.py) target the maintainer's local Relay installation and hostnames. Set RELAY_SESSION_COOKIE to an existing administrator session cookie (the full Cookie header value). Public with members uses browser sessions, not API bearer tokens. The scripts use temporary containers for live log and action checks and remove their fixtures. Adapt their local configuration before running on another server; credentials are never part of the repository.

API

Pages, assets, health, and filtered metrics are public. Logs and action endpoints require the trusted administrator header:

Endpoint Purpose
GET /api/metrics Role-filtered host snapshot with recent history (full Docker data for admins)
GET /api/events Live SSE metrics events
GET /api/containers/{hex-id}/logs SSE log and end events
GET /api/projects/{project}/logs Combined project log stream
POST /api/containers/{hex-id}/actions Container lifecycle action
POST /api/projects/{project}/actions Project lifecycle action
GET /api/health Internal health check; no application authentication

Action requests require Content-Type: application/json, X-Monitor-Action: 1, and a body such as {"action":"restart"}. Valid actions are start, stop, and restart.

Page routes include /overview, /containers, /hardware, /temperatures, /ups, /processes, /storage, and /network; refresh and browser back/forward preserve navigation.

Contributing

Bug reports and pull requests are welcome. Include reproduction steps and relevant host/Docker details, run the checks above, and keep credentials and private logs out of reports. Contributions are licensed under Apache-2.0.

License

Copyright 2026 Sebastjan Godnič. Hostwatch is licensed under the Apache License 2.0. Vendored Alpine.js retains its MIT license. See NOTICE for attribution.

Compose file editing

Relay administrators can use Edit Compose on a project in the Containers tab to open the files recorded in its Docker labels, including override files. New project creates <default folder>/<project name>/compose.yml. The YAML input starts empty. Set the default folder in that form; directory suggestions and an existence check help select it. The folder must already exist before creating or deploying a project, and is remembered in the compose-settings volume. Projects in that folder appear in a separate list below deployed projects, with matching row styling and a Deploy button; running projects are matched by their recorded Compose paths as well as their names. COMPOSE_PROJECTS_FOLDER sets the initial default (/srv/compose, or the configured host root when restricted).

The editor checks YAML syntax and requires a top-level mapping (including Compose overrides and fragments). It preserves file ownership and permissions when replacing an existing file, detects stale edits, and never overwrites an existing file when creating one. Save only writes the file. Save and deploy for new projects, or Save and redeploy for existing projects, saves it and runs docker compose up -d --build, showing live deployment status and command output. Successful deployments close the editor and open live project logs automatically; failed deployments keep their output visible. Existing projects retain their recorded project name, working directory, and all Compose files; new files use Compose’s normal project naming. Deployment errors leave the saved file available for correction. The editor opens at 60% of desktop viewport height and can be resized. Delete project previews the Compose files and requires confirmation before deleting them. Optionally remove their folder when it contains only those files; folders containing data or other files are kept. Deletion leaves running containers and Docker volumes intact.

The Compose deployment defaults to accessing the host filesystem (COMPOSE_HOST_ROOT=/). Set COMPOSE_HOST_ROOT to an absolute host directory in your environment or .env to restrict file access and writing to that directory; Compose uses it for both the bind mount and displayed host paths. No installation-specific paths are required. For other deployments, COMPOSE_ROOT is the accessible host folder root and COMPOSE_MOUNT is its corresponding writable container mount (default /compose-host).

Host symlinks, including absolute links, are resolved in the host filesystem namespace. Targets outside a configured folder root are rejected. Editing requires write access to that mount; the rest of the container remains read-only. The editor uses the same Relay administrator check and action header as container lifecycle commands.

Redeployments run in a temporary helper container using static Docker tools from the Hostwatch image. Host directories are mounted read-only at their original paths so relative build contexts, .env files, symlinks, and host bind mounts work as they do with the host CLI. Compose manages the host Docker daemon through its existing socket. Set COMPOSE_RUNNER_IMAGE to the image used for your Hostwatch deployment (default system-monitor:local); custom images must include /compose-tools/docker and /compose-tools/cli-plugins/docker-compose, and /compose-tools/cli-plugins/docker-buildx, as the supplied Dockerfile does. The monitoring host mount (HOST_ROOT, default /host) is required. The helper stays independent of the app, allowing deployment status to survive a restart of Hostwatch itself. The most recent 20 completed helpers are retained for status and logs; older completed helpers are removed on the next deployment.