A minimal, self-hosted SMART disk health monitor
3.4K
This repository requires mandatory token authentication for security. If you don't add your token to the .env file, the installation will fail.
SMART disk monitoring — single static Go binary + a small smartctl collector, no reverse proxy, no middleware.
Two images ship together as a pair, always the same version tag:
raymontool — HTTP server + SQLite + web UI. scratch base, no shell.raymontool-collector — runs smartctl against your disks and writes the results for the server to read.mkdir -p data/smart data/db
services:
raymontool:
image: b3rkay/raymontool:latest
container_name: raymontool
restart: unless-stopped
network_mode: host
volumes:
- ./data/smart:/data/smart:ro
- ./raymontool-db:/data
environment:
- RMT_COLLECTOR_TOKEN=${RMT_COLLECTOR_TOKEN}
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "/raymontool", "-healthcheck"]
interval: 30s
timeout: 3s
start_period: 10s
retries: 3
collector:
image: b3rkay/raymontool-collector:latest
container_name: raymontool-collector
restart: unless-stopped
network_mode: host
privileged: true
volumes:
- ./data/smart:/data/smart
- /dev:/dev
- /:/hostroot:ro
environment:
- RAYMONTOOL_DATA_DIR=/data/smart
- RMT_COLLECTOR_TOKEN=${RMT_COLLECTOR_TOKEN}
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "/raymontool-collector", "-healthcheck"]
interval: 30s
timeout: 3s
start_period: 10s
retries: 3
docker compose up -d
Open http://<host>:2222.
The server never touches disk hardware and never runs smartctl — it only reads JSON the collector already wrote to /data/smart. Only the collector needs raw /dev access and privileged: true, so only the collector carries that risk. The server stays on scratch, unprivileged, read-only on the shared volume.
If you want to harden this further, you can isolate the collector under a dedicated system user. Create a unprivileged user (e.g. smartmon) on the host, note its UID/GID, and pass them into the container:
collector:
user: "1500:1500" # smartmon UID:GID
privileged: true
This way, even though the container needs privileged: true for smartctl, the process inside runs as smartmon rather than root. If the container is ever compromised, the attacker lands as an unprivileged host user with no meaningful permissions outside the container's scope.
smartctl ships inside the collector image already (smartmontools, installed at build time) — nothing to install on the host.privileged: true and /dev:/dev to reach the disks. Without both, it starts but reports nothing.latest — most recent build.<version> — immutable, matches a specific release. Pin this in production; roll back by re-deploying an older tag.Server and collector are always pushed as a matched pair under the same tag — never mix versions between the two.
2222 — server HTTP/UI.2223 — collector's internal /trigger endpoint (manual "refresh now").Both containers use network_mode: host, so no ports: mapping is needed or used.
| Path | Container | Purpose |
|---|---|---|
/data/smart | both | collector writes, server reads (mount read-only on the server side) |
/data | server | SQLite database, persists across container recreation |
/dev | collector | raw disk access for smartctl |
Both images include a built-in HEALTHCHECK (-healthcheck, checking each process's own /healthz on its trigger port) — no curl/wget needed since neither base image reliably has both a shell and curl. The collector's check only proves its trigger server (:2223) is alive and responsive, not that the last smartctl pass actually succeeded — for that, still watch its logs and the mtimes under /data/smart.
linux/amd64, linux/arm64.
Content type
Image
Digest
sha256:1d6f682a5…
Size
6.1 MB
Last updated
22 days ago
docker pull b3rkay/raymontool