homebutler
This MCP server exposes HomeButler's homelab operations toolkit, letting an agent inspect health, Docker, backups, apps, Proxmox, watches, and reports, with some tools performing destructive changes.
Inspect server state: system status, doctor diagnostics, alerts and alert history, config validation, open ports, processes, network scan, inventory/topology export, and change reports.
Manage Docker: list, inspect, view logs/stats/top processes, restart, and stop containers.
Handle backups: create backups, list archives, run isolated restore drills, and restore volumes destructively.
Manage self-hosted apps: list catalog, install, check status, uninstall while keeping data, or purge data.
Operate Proxmox VE: read cluster/node/guest/task status and start, reboot, or gracefully shut down explicitly confirmed guests.
Monitor restarts: add/remove/list watched Docker, systemd, or PM2 targets; run one-shot checks; browse incident history with optional logs.
Notify and wake: send test notifications through configured channels and Wake-on-LAN packets.
Generate reports: produce health/change reports with snapshot comparison, optionally previewing without saving a new snapshot.
Most tools accept a
serverparameter to target a configured remote server.
Enables comprehensive management of Docker environments, allowing users to list, restart, and stop containers, view logs, and perform backups of Docker volumes, compose files, and environment configurations.
That is the whole idea. Most homelab tools show you a graph of right now, and leave "does this matter?" to you. HomeButler remembers what your server looked like last time, decides what is worth saying, and says it โ six containers before and six after is not "no change" when one of them is a different container.
Reading a report
homebutler report
homebutler report --keep 7 # retain only the latest 7 snapshots
homebutler report --no-save # preview without writing a snapshotreport gives you a concise butler-style summary of your homelab: current health, warnings, notable changes since the previous snapshot, and suggested next commands. On the first run, HomeButler creates a baseline under ~/.homebutler/reports/snapshots/; later runs compare against the latest snapshot. Old snapshots are pruned automatically (--keep 30 by default) so reports do not grow forever.
Section rules, labels, and severities are colour-coded in a terminal. Colour is dropped automatically when output is piped, redirected, or run from cron.
Reading a change
Every change line, like those in the card at the top, is three columns: what kind of change, what it happened to, and
what exactly happened. The kind is one of eight words, and it is the same word in
--json, so an agent branches on it without reading prose:
Kind | Means | You would see it after |
| it was there last time and is not now |
|
| it was not there last time and is now | starting anything |
| same name, different thing underneath |
|
| same container, different image | pulling a new tag |
| same container, running where it was stopped, or the reverse; or a process that keeps restarting | a crash, a crash loop, or bringing something back up |
| same port, a different process answering on it | one service taking over another's port |
| a mount moved by more than half a gigabyte | anything that writes |
| the comparison could not be made | Docker was down when either snapshot was taken |
replaced is the one the rest of this exists for. A container recreated under the
same name leaves every count identical, which is why a report that compares counts โ
as this one did before 0.26.0 โ answers "no significant changes" while the thing you
were running has been swapped out underneath you.
skipped is the second: homebutler says it could not compare rather than reporting
nothing changed. An all-clear it cannot stand behind is worse than no answer.
The header names the snapshot being compared against, so "what changed" is never ambiguous about the window it covers.
๐ What earns a line, and what is deliberately suppressed โ
Related MCP server: Plutus MCP Server
See it run
Why homebutler?
HomeButler helps you answer the boring but painful questions every homelab eventually creates:
What is running on my server right now?
Which container owns this port?
Why did this service restart at 3 AM?
Is my backup actually restorable?
Can I install this self-hosted app without hand-writing another compose file?
Can I let an AI assistant inspect my server without handing it a full SSH shell?
No daemon required, no database. Run it from a terminal, cron, a web dashboard you start when you want one, or an AI agent.
The design goal is simple: give humans and agents a narrow, structured interface to the server. HomeButler returns readable summaries and JSON instead of asking you to trust a black-box shell session.
Self-hosting is not hard because one docker compose up is hard. It is hard because the maintenance never ends: ports collide, containers restart silently, backups look fine until restore day, and every server becomes a slightly different snowflake.
HomeButler is a small operations toolkit for that messy middle.
Alongside what you already run
Keep Uptime Kuma for is it up, Beszel or Netdata for the graph, Dozzle for the logs. HomeButler answers the question none of them ask: what is different from last time, and does it matter?
No agent on the machines it watches. The binary is put there once with
homebutler deployand runs only when asked, over SSH โ no daemon, no open port, nothing listening between runs.The judgement is a written rule, not a model. What earns a line is in docs/report.md, and the same input gives the same report โ no AI required, no account, no paid tier.
It checks that a backup comes back. A backup you have never restored is a folder.
backup drillboots the archive as a second copy of the app, on a network and port of its own, and requires it to answer an HTTP health check before calling the archive good โ then removes everything it made. A failed drill exits non-zero, sobackup drill --allbelongs in the same cron entry asbackup. docs/backup.md has what it does and what it does not prove.๐ Integrity: โ tar valid (8 files) ๐ Boot: โ container started in 0s ๐ Health: โ HTTP 200 on port 60405 โ DRILL PASSED
Quick Start
# One-line install (auto-detects OS/arch)
curl -fsSL https://raw.githubusercontent.com/Higangssh/homebutler/main/install.sh | sh
# Or via Homebrew
brew install Higangssh/homebutler/homebutler
# Or as a container โ the hub that reaches your machines over SSH
docker run -d --name homebutler -p 8080:8080 -v ~/.config/homebutler:/config -v ~/.ssh:/root/.ssh:ro \
-e HOMEBUTLER_TOKEN="$(openssl rand -hex 16)" ghcr.io/higangssh/homebutler
docker exec homebutler printenv HOMEBUTLER_TOKEN # the token, to sign in to the dashboard
# Interactive setup โ add your servers in seconds
homebutler initOr as a container, reaching your machines over SSH โ docs/docker.md:
docker run -p 8080:8080 -e HOMEBUTLER_TOKEN=โฆ -v ~/.config/homebutler:/config -v ~/.ssh:/root/.ssh:ro ghcr.io/higangssh/homebutler
Use it right away:
homebutler status # CPU, memory, disk, uptime
homebutler docker list # running containers
homebutler inventory scan # containers + ports + topology
homebutler report # butler-style health report + change summary
homebutler install uptime-kuma # deploy a self-hosted app
homebutler backup drill uptime-kuma # verify a backup actually restores
homebutler watch tui # terminal dashboard
homebutler serve # web dashboard at http://localhost:8080Machine-readable output is available everywhere:
homebutler status --json
homebutler inventory scan --json
homebutler report --jsonWhat else it does
Install apps โ deploy Uptime Kuma, Jellyfin, Pi-hole, Gitea, Portainer, and more with one command
Map your server โ see containers, exposed ports, system ports, and service topology
Run a doctor check โ diagnose resource pressure, stopped containers, public ports, backup hygiene, notifications, report baseline readiness, and configured Proxmox endpoint reachability
Catch crashes โ save logs before/after Docker, systemd, or PM2 restarts and detect flapping loops
Verify backups โ boot backups in isolated containers before you trust them
See a Proxmox cluster โ nodes, QEMU and LXC guests, storage, task status, and honest dashboard freshness, with power actions that name their target explicitly
Use it anywhere โ CLI, JSON, web dashboard, or MCP for AI agents without giving them SSH
๐ฉบ Doctor Check
homebutler doctor
homebutler doctor --strict # non-zero exit if warnings/failures are found
homebutler doctor --json # automation / MCP friendlydoctor is a read-only preflight for the problems homelab users usually discover too late: high disk or memory usage, stopped containers, public bind ports, stale or missing backups, missing notifications, whether report has a baseline for change detection, and whether each configured Proxmox endpoint is reachable with the token it has. Every finding names the next command to run, so --strict makes it usable from cron or CI โ including a Proxmox host that is unreachable or rebooting.
Every finding carries a category, and --json carries the same word, so a
caller filters on it without reading the title:
Category | What it is about |
| CPU, memory, disk |
| containers that are stopped or unhealthy |
| ports listening on every interface |
| backups missing, stale, never drilled, or growing without a retention limit |
| whether there is a baseline to compare against |
| targets listed with nothing polling them |
| channels configured, or never tested |
| an endpoint that cannot be reached with the token it has |
| the config file itself โ permissions, keys homebutler does not know |
| a collector did not answer, so this diagnosis is partial |
| nothing specific: the all-clear |
incomplete is the one worth handling. It means the answer you are reading is
missing a section, which is different from that section being fine.
๐ Config Validation
homebutler config validate
homebutler config validate --strict # exit non-zero on warnings too
homebutler config validate --jsonconfig validate reads your config without starting anything and tells you
which file was used, which of the four resolution rules picked it, and what
homebutler actually made of each section. It exists because the two ways config
goes wrong are both silent: a key homebutler does not recognise is dropped
without a word, and a --config path that does not exist falls back to
built-in defaults rather than failing.
Sections
โ servers 2 servers (homelab, nas)
ยท notify not set
โ alerts cpu 95% ยท memory 85% ยท disk 90%
Findings
โ ๏ธ Line 5: field notifiy not found in the homebutler config
โ Did you mean "notify"? Unrecognised keys are ignored silently.Watch and alerts
๐ Process Restart Watch
Your container crashed at 3 AM โ but why? homebutler watch catches it the moment it happens, saves the dying logs, figures out the cause, and tells you if it's happening over and over.
Supported backends: Docker (real-time event stream) ยท systemd (polling) ยท PM2 (polling)
Step 1: Add targets to watch
homebutler watch add nginx # Interactive: choose Docker / systemd / PM2
homebutler watch add --kind docker nginx # or specify directly
homebutler watch add --kind systemd nginx.service
homebutler watch add --kind pm2 my-api
homebutler watch add --kind systemd web --unit nginx.service # listed as web, checked as nginx.service
homebutler watch list # See what you're watchingStep 2: Start monitoring
homebutler watch start # Foreground, Ctrl+C to stop
homebutler watch start --interval 10s # Custom poll interval (default 30s)homebutler watch install # register it with systemd or launchd
homebutler watch installed # is it registered?
homebutler watch uninstallwatch install hands the loop to whatever supervises the host โ a systemd user
unit on Linux, a launchd agent on macOS โ so monitoring survives logout and
reboot. Both are user-level and neither is a preference: on Linux the watch list
lives in your home directory, so a root unit would find an empty list; on macOS
Docker Desktop only runs inside a logged-in session, so a LaunchDaemon would
poll a daemon that is not there. On Linux a user unit stops at logout unless
lingering is on; watch install checks, and if it is off tells you to run
loginctl enable-linger $USER (with sudo only if your system refuses it).
watch start is the monitoring process. It watches the containers and services
on the watch list for restarts, checks CPU, memory and disk against your
thresholds, and runs any remediation rules you have configured โ one process,
one set of notification providers. alerts --watch still exists and does the
threshold half on its own.
Every endpoint under proxmox: in your config is polled too: unreachable or
ACL-filtered endpoints and any guest listed under that endpoint's guests:
report one incident when the problem starts and one recovery incident when it
clears. A guest not listed there is observational only โ watch start never
alerts on it, deliberately stopped or not. See
Proxmox setup โ for the guests: field.
When a crash is detected, you'll see:
[03:14:22] INCIDENT: nginx (incident nginx-20260410-031422.581-7a2124)
Crash: OOM โ process killed by SIGKILL (oom, confidence: high)
โ FLAPPING: acute (3 restarts in short window)Step 3: Investigate
homebutler watch history # List all incidents
homebutler watch show <incident-id> # Full detailswatch show output includes:
Pre-death logs โ what the process printed right before it died
Post-restart logs โ what happened after the restart
Crash analysis โ category (oom / panic / segfault / timeout / dependency / error), reason, confidence level, matched log patterns
Flapping status โ if the process is stuck in a crash loop
Crash Analysis
Every incident is automatically analyzed using exit codes and log patterns:
Signal | Exit Code | Meaning |
SIGKILL | 137 | OOM Killer or forced kill |
SIGSEGV | 139 | Segmentation fault (memory corruption) |
SIGTERM | 143 | Graceful shutdown request |
โ | 1 | Application error |
โ | 0 | Clean exit (may be intentional restart) |
Log patterns like panic:, Out of memory, Connection refused, FATAL, and timeout are matched automatically to help identify the root cause.
Flapping Detection
Detects when a process is stuck in a restart loop (e.g., crash โ restart โ crash again):
Acute โ 3+ restarts within 10 minutes (something is broken right now)
Chronic โ 5+ restarts within 24 hours (slow recurring issue)
Flapping incidents are tagged [FLAPPING] in history and highlighted in watch show.
Notifications (optional, off by default)
Notifications are disabled by default, which is useful for air-gapped or closed networks where everything runs locally.
A minimal example in ~/.config/homebutler/config.yaml:
notify:
telegram:
bot_token: "your-bot-token"
chat_id: "your-chat-id"
watch:
enabled: true
notify_on: flapping
cooldown: 5m
flapping:
short_window: 10m
short_threshold: 3
long_window: 24h
long_threshold: 5
retention:
max_incidents: 200
alerts:
cpu: 90
memory: 85
disk: 90
rules:
- name: cpu-spike
metric: cpu
threshold: 90
action: notify
- name: elsa-monitor-down
metric: container
kind: systemd # docker (default) | systemd | pm2
watch: [lh-elsa-monitor.service]
action: restartRestarting things that are not containers
action: restart restarts Docker containers unless the rule says otherwise.
kind: systemd or kind: pm2 points it at a service or a PM2 app instead.
The kind is written on the rule rather than looked up from the watch list, so
restarting a host service is something you asked for in the config. It also
means every rule written before kind existed keeps meaning exactly what it
meant.
Two things worth knowing before using it:
systemctl restart needs root or a polkit rule. Running homebutler
unprivileged, a systemd restart will be refused, reported as failed, and
warned about when alerts --watch starts rather than when the rule first
fires.
A target that is flapping is not restarted. Restarting something already
in a restart loop feeds the loop, and most systemd units carry
Restart=always, so homebutler restarting them fights systemd's own backoff.
The thresholds are the watch.flapping ones above, and the skip is reported
rather than counted as either success or failure. This applies to Docker
targets too.
Legacy ~/.homebutler/watch/config.json is still read as a fallback for watch-specific settings, and legacy alerts.yaml notify/webhook provider settings are still accepted for older setups.
watch.enabled: trueโ allow watch notificationswatch.notify_on: flappingโ notify only when repeated restart loops are detectedwatch.notify_on: incidentโ notify on every incidentwatch.notify_on: allโ notify on both incidents and flappingwatch.notify_on: offโ disable watch notifications without removing provider configwatch.cooldown: 5mโ suppress duplicate notifications for the same event fingerprint during the cooldown windowwatch.flappingโ optional advanced tuning for restart-loop detectionwatch.retention.max_incidents: 200โ how many incidents to keep on disk, newest first. The directory grows fastest exactly when a service is restarting in a loop. Set-1to keep everything.Each incident keeps up to 100 captured log lines per side, and at most 64 KB of them. Line counts alone do not bound a file: one stack trace or JSON document on a single line is arbitrarily long, and a container being OOM-killed is exactly the one likely to write one. A log that does not fit keeps its end โ the last thing a process said is what explains why it stopped โ and says how much was dropped.
These settings can also be written under a watch.notify: block, which is the
canonical form:
watch:
notify:
enabled: true
notify_on: flapping
cooldown: 5m
flapping:
short_window: 10mBoth spellings are read, so either layout works. If a file contains both, the
notify: block wins and homebutler config validate says so.
Manage targets
homebutler watch remove nginx # Stop watching
homebutler watch check # One-shot check (no continuous monitoring)Alert Thresholds (Advanced)
alerts still exists for CPU, memory, and disk threshold checks, but it is an advanced flow and not the recommended first step for new users.
homebutler alerts --watch # default: 30s interval
homebutler alerts --watch --interval 10s # check every 10 seconds
homebutler alerts history # view alert history
homebutler notify test # test your notification channelsDefault thresholds: CPU 90%, Memory 85%, Disk 90%. Start with watch, then add alerts only if you specifically want threshold-based checks.
Backup & Restore
One-command Docker backup โ volumes, compose files, and env variables.
homebutler backup # backup everything
homebutler backup --service jellyfin # specific service
homebutler backup --to /mnt/nas/backups/ # custom destination
homebutler backup list # list backups
homebutler restore ./backup.tar.gz # restoreโ ๏ธ Database services should be paused before backup for data consistency.
๐ Full backup documentation โ โ how it works, archive structure, security notes.
๐ Backup Drill
"Having a backup" and "being able to restore" are different things.
Backup Drill boots your backup in an isolated Docker environment and verifies the app actually responds โ like a fire drill for your data. A failed drill exits non-zero.
homebutler backup drill uptime-kuma # verify one app
homebutler backup drill --all # verify all apps
homebutler backup drill --json # machine-readable output
homebutler backup drill --archive ./file # use a specific backupFull walkthrough, including what a passing drill does not prove: docs/backup.md.
What happens:
Finds the latest backup archive
Verifies archive integrity (
tarvalidation)Creates an isolated Docker network + random port
Boots the app from backup data
Runs an HTTP health check
Reports pass/fail and cleans up everything
๐ Backup Drill โ uptime-kuma
๐ฆ Backup: ~/.homebutler/backups/backup_2026-04-04_1711.tar.gz
๐ Size: 18.6 MB
๐ Integrity: โ
tar valid (8 files)
๐ Boot: โ
container started in 0s
๐ Health: โ
HTTP 200 on port 58574
โฑ๏ธ Total: 2s
โ
DRILL PASSEDZero risk โ runs in a completely isolated environment. Your running services are never touched.
Supports health checks for: nginx-proxy-manager, vaultwarden, uptime-kuma, pi-hole, gitea, jellyfin, plex, portainer, homepage, adguard-home.
App Install
$ homebutler install list
๐ฆ Available apps (15):
uptime-kuma A self-hosted monitoring tool
vaultwarden Lightweight Bitwarden-compatible password manager
jellyfin Free software media system for streaming movies, TV, and music
pi-hole Network-wide ad blocking via DNS filtering
โฆ
homebutler install uptime-kumaโ Deploy self-hosted apps in seconds. Pre-checks Docker, ports, and duplicates. Generatesdocker-compose.ymlautomatically. See all available apps โ
Deploy self-hosted apps with a single command. Each app runs via docker compose with automatic pre-checks, health verification, and clean lifecycle management.
# List available apps
homebutler install list
# Install (default port)
homebutler install uptime-kuma
# Install with custom port
homebutler install uptime-kuma --port 8080
# Install jellyfin with media directory
homebutler install jellyfin --media /mnt/movies
# Check status
homebutler install status uptime-kuma
# Stop (data preserved)
homebutler install uninstall uptime-kuma
# Stop + delete everything
homebutler install purge uptime-kumaHow it works
~/.homebutler/apps/
โโโ uptime-kuma/
โโโ docker-compose.yml โ auto-generated, editable
โโโ data/ โ persistent data (bind mount)Pre-checks โ Verifies docker is installed/running, port is available, no duplicate containers
Compose-based โ Each app gets its own
docker-compose.ymlyou can inspect and customizeData safety โ
uninstallstops containers but keeps your data;purgeremoves everything, including the Docker volumes the app used, and names themCross-platform โ Auto-detects docker socket (default, colima, podman)
Available apps
App | Default Port | Description | Notes |
| 3001 | Self-hosted monitoring tool | |
| 32400 | Plex Media Server |
|
| 8080 | Bitwarden-compatible password manager | |
| 8081 | Web-based file manager | Its upstream project is archived and gets no further releases or security fixes |
| 8082 | Developer utilities (JSON, Base64, Hash, etc.) | |
| 3002 | Lightweight self-hosted Git service | |
| 8096 | Media system (movies, TV, music) |
|
| 3010 | Modern homelab dashboard | |
| 8083 | All-in-one PDF tool (merge, split, convert, OCR) | |
| 8084 | Internet speed test with historical graphs | |
| 9925 | Recipe manager and meal planner | |
| 8088 | DNS ad blocking | โ ๏ธ Uses port 53 (DNS), NET_ADMIN capability |
| 3000 | DNS ad blocker and privacy | โ ๏ธ Uses port 53 (DNS) |
| 9443 | Docker management GUI | โ ๏ธ Mounts Docker socket (HTTPS) |
| 81 | Reverse proxy with SSL and web UI | โ ๏ธ Uses ports 80/443 |
App-specific options
# Jellyfin: mount your media library
homebutler install jellyfin --media /mnt/movies
# Pi-hole / AdGuard: DNS ad blocking (port 53 required)
homebutler install pi-hole
# โ ๏ธ If port 53 is in use (Linux): sudo systemctl disable --now systemd-resolved
# Portainer: Docker GUI (mounts docker socket)
homebutler install portainer
# Access via HTTPS: https://localhost:9443
# Nginx Proxy Manager: reverse proxy
homebutler install nginx-proxy-manager
# Default login: admin@example.com / changeme (change immediately!)
# Any app: custom port
homebutler install <app> --port 9999Safety checks
Port conflict detection โ Checks if the port is already in use before install
DNS mutual exclusion โ Warns if pi-hole and adguard-home are both installed
Docker socket warning โ Alerts when an app requires Docker socket access (portainer)
OS-specific guidance โ Linux gets systemd-resolved fix, macOS gets lsof command
Post-install tips โ DNS setup, HTTPS access, default credential warnings
Want more apps? Open an issue or see Contributing.
Dashboards
๐ Web Dashboard
homebutler serveโ A web dashboard embedded in the single binary viago:embed. Servers, Docker containers, open ports, alerts and Wake-on-LAN from any browser. With--tokenit also edits the config: thresholds, notification channels, servers, Proxmox endpoints and wake devices.
The Report tab, where you actually read it
The screen somebody opens after a notification. What needs attention first, then what moved with the kind word each change carries, then the snapshot being compared against and how old it is. A comparison that could not be made says so rather than reading as an all-clear.
Loading it saves nothing โ a page that took a snapshot every time it was opened would prune the baseline you wanted to compare against. Saving one is a button.
Server Overview โ See all servers at a glance with color-coded status (green = online, red = offline)
System Metrics โ CPU, memory, disk usage with progress bars and color thresholds
Docker Containers โ Running/stopped status with friendly labels ("Running ยท 4d", "Stopped ยท 6h ago")
Top Processes โ Top processes sorted by CPU/memory with zombie detection
Resource Warnings โ Visual CPU, memory, and disk thresholds in the dashboard
Network Ports โ Open ports with process names and bind addresses
Wake-on-LAN โ One-click wake buttons for configured devices
Server Switching โ Dropdown to switch between local and remote servers
Zero dependencies โ No Node.js runtime needed. Frontend is compiled into the Go binary at build time
homebutler serve # Start on port 8080
homebutler serve --port 3000 # Custom port
homebutler serve --demo # Demo mode with realistic sample data
homebutler serve install # Let the host keep it runningIn Homepage
Already run Homepage? One customapi snippet in
services.yaml puts a homebutler card on it: needs attention, changes since
the last snapshot, containers running and stopped.
The snippet โ
๐ฅ๏ธ TUI Dashboard
homebutler watch tuiโ A terminal-based dashboard powered by Bubble Tea. Monitors all configured servers with real-time updates, color-coded resource bars, and Docker container status. No browser needed.
๐บ๏ธ Inventory & Topology
homebutler inventory scan
homebutler inventory show --filter exposed
homebutler inventory export --format mermaid
homebutler --json inventory scaninventory scan gives you a quick map of what is running on a server: system health, Docker containers, app ports, and system ports. Docker-published ports are connected back to the container that owns them, so local forwarding details like Colima/Lima stay understandable.
๐ Home Network
Server homelab (192.168.1.10)
Summary โ
1 running ยท โช 1 stopped ยท ๐ 2 public ports ยท ๐ 4 local ports
๐ฆ Containers (2)
โโ โช vaultwarden ยท not started
โ โโ image vaultwarden/server:latest
โโ โ
api-server ยท running
โโ image my-api:latest
โโ exposes :8080 โ 8080/tcp
๐ App Ports (1)
โโ ๐ :8080/tcp ยท api-serverTo answer "what is reachable from outside my machine/network?" without reading the whole tree, filter the scan to exposed ports only:
homebutler inventory scan --filter exposed๐ Home Network
Server homelab
๐ Exposed Ports
โโ :8080/tcp ยท api-server
โโ :8443/tcp ยท dashboardOnly ports listening on all interfaces (0.0.0.0, ::, *) are shown. Anything bound to a specific address is hidden, including loopback and LAN addresses. Unsupported filter values return an error, as does combining --filter with --json; the default inventory scan output is unchanged.
Use Mermaid export when you want a diagram for GitHub, Obsidian, docs, or an AI assistant:
graph TD
home["๐ Home Network"] --> homelab["๐ฅ homelab<br/>192.168.1.10"]
homelab --> c1["๐ฆ api-server<br/>running"]
homelab --> p1["๐ :8080/tcp<br/>api-server"]
c1 -. exposes .-> p1๐ง Proxmox VE
homebutler proxmox status
homebutler proxmox guests --status running
homebutler proxmox guest shutdown --node pve1 --type lxc --vmid 105 --confirm
homebutler proxmox task UPID:pve1:... --node pve1A Proxmox endpoint is its own kind of target, configured under proxmox: with an
API token rather than SSH, so it does not join the --server or --all fan-out.
TLS verification stays on: trust comes from a pinned SHA-256 fingerprint, then a
CA file, and only then an explicit insecure fallback.
Reads are plain. Power actions are not: every one of them takes an explicit
endpoint, node, guest type and VMID, and refuses to run without --confirm,
which is checked before any credential is read. They also need their own
action_token_id (plus action_token or action_token_file) configured on
the endpoint โ the read token alone will not start, reboot, or shut down a
guest; see Proxmox setup โ for creating that second token.
shutdown asks the guest to shut down cleanly โ it is not Proxmox's hard
stop, which cuts power and can leave a filesystem behind it. A successful
action reports the task it submitted, not that the guest finished; proxmox task answers that separately.
proxmox script prints the install command for a Community Script pinned to one
commit, along with a warning that the script is not reviewed by homebutler and
runs as root. It never fetches or runs it โ see #62
for why that line is where it is.
๐ Proxmox setup, tokens, and TLS โ
Installation
Homebrew (Recommended)
brew install Higangssh/homebutler/homebutlerAutomatically installs to PATH. Works on macOS and Linux.
One-line Install
curl -fsSL https://raw.githubusercontent.com/Higangssh/homebutler/main/install.sh | shAuto-detects OS/architecture, downloads the latest release, and installs to PATH. macOS and Linux only; on Windows, use the zip below.
Windows
Download homebutler_<version>_windows_amd64.zip (or _windows_arm64.zip)
from the latest release,
extract homebutler.exe, and put it in a folder on your PATH.
homebutler upgrade replaces it with the next release's. The npm package
needs Windows 10 version 1803 or later, whose tar.exe it extracts the zip with.
On Windows, status and what is built on it read this machine, and ports and
processes say they are not supported. Servers in your config are reached
over SSH as from any other machine, with keys read from
C:\Users\<you>\.ssh (configuration).
npm (MCP server)
npm install -g homebutlerDownloads the Go binary automatically. This package publishes one command,
homebutler-mcp, and it starts the MCP server โ npx -y homebutler@latest
launches that, not the CLI. For the command line, use one of the other
installs above.
Claude Desktop extension (MCP server)
Download homebutler_<version>_darwin_arm64.mcpb (Apple silicon),
homebutler_<version>_darwin_amd64.mcpb (Intel Mac),
homebutler_<version>_windows_amd64.mcpb (Windows) or
homebutler_<version>_windows_arm64.mcpb (Windows on Arm) from the
latest release and
open it, or drag it onto Claude Desktop's Settings โ Extensions. The binary is
inside, so nothing else needs installing. It starts read-only; see
the MCP docs to allow more.
Go Install
go install github.com/Higangssh/homebutler@latestBuild from Source
git clone https://github.com/Higangssh/homebutler.git
cd homebutler
make build-allmake build-all compiles the web dashboard into the binary and needs Node
installed. make build skips it โ the CLI is complete either way, and
homebutler serve then says the dashboard is missing and names the two ways
to get it.
Uninstall
rm $(which homebutler) # Remove binary
rm -rf ~/.config/homebutler # Remove config (optional)Usage
homebutler <command> [flags]
Commands:
status System status (CPU, memory, disk, uptime)
doctor Diagnose health, exposure, backups, and readiness
config validate Check the config file and report what is ignored
docker list List running containers
install <app> Install a self-hosted app (docker compose)
alerts Show current alert status
watch tui TUI dashboard (monitors all configured servers)
watch add/list/remove Manage watched containers
watch check/start One-shot or continuous restart detection
watch history/show Browse restart history
proxmox status Proxmox VE cluster, nodes, guests, and storage
serve Web dashboard (browser-based, go:embed)
Flags:
--json JSON output (default: human-readable)
--verbose, -v Show detailed error information
--server <name> Run on a specific remote server
--all Run on all configured servers in parallel
--port <number> Port for serve command (default: 8080)
--config <path> Config file (auto-detected, see Configuration)Run homebutler --help for all commands.
Commands:
init Interactive setup wizard
config validate Check the config file and report what is ignored
status System status (CPU, memory, disk, uptime)
doctor Diagnose health, exposure, backups, and readiness
watch tui TUI dashboard (monitors all configured servers)
watch add <name> Add container to restart watch list
watch list Show watched containers
watch remove <name> Remove container from watch list
watch check One-shot restart check
watch start Continuous monitoring: restarts, thresholds, rules
watch install Register watch with systemd or launchd
watch installed Report whether it is registered
watch uninstall Remove the service unit
watch history List restart history (alias: incidents)
watch show <id> Show restart details with logs
serve Web dashboard (browser-based, go:embed)
docker list List running containers
docker restart <n> Restart a container
docker stop <n> Stop a container
docker logs <n> Show container logs
docker top <n> Show processes running inside a container
docker inspect <n> Show image, state, ports, mounts, networks, health
report What changed since the last snapshot
inventory scan Map containers, ports, and topology
inventory show Same as scan (--filter exposed narrows it)
inventory export Export the map (--format mermaid)
proxmox status Proxmox VE cluster, nodes, guests, storage
proxmox guests List QEMU and LXC guests
proxmox node <n> Node detail
proxmox guest ... start / shutdown / reboot (needs --confirm)
proxmox task <upid> Task status for an action already submitted
proxmox tasks Recent tasks on a node
proxmox script Community Script install commands (prints, never runs)
notify test Send a test notification through configured providers
wake <name> Send Wake-on-LAN packet
ports List open ports with process info
ps Show top processes (alias: processes)
ps --sort mem Sort by memory instead of CPU
ps --limit 20 Show top 20 (default: 10, 0 = all)
network scan Discover devices on LAN
alerts Show current alert status
alerts --watch Thresholds only (watch start covers these too)
trust <server> Register SSH host key (TOFU)
backup Backup Docker volumes, compose files, and env
backup list List existing backups
backup drill <app> Verify backup restores correctly (isolated)
backup drill --all Verify all apps in backup
restore <archive> Restore from a backup archive
upgrade Upgrade local + all remote servers to latest
deploy Install homebutler on remote servers
install <app> Install a self-hosted app (docker compose)
install list List available apps
install status <a> Check installed app status
install uninstall Stop app (keep data)
install purge Stop app + delete all data
mcp Start MCP server (JSON-RPC over stdio)
version Print version
Flags:
--json JSON output (default: human-readable)
--verbose, -v Show detailed error information
--server <name> Run on a specific remote server
--all Run on all configured servers in parallel
--port <number> Port for serve command (default: 8080)
--demo Run serve with realistic demo data
--watch Continuous monitoring mode (alerts command)
--interval <dur> Watch interval, e.g. 30s, 1m (default: 30s)
--config <path> Config file (auto-detected, see Configuration)
--local Upgrade only the local binary (skip remote servers)
--local <path> Use local binary for deploy (air-gapped)
--service <name> Target a specific Docker service (backup/restore)
--allow-bind <path> Host path a restore may write a bind mount to (repeatable)
--endpoint <name> Proxmox endpoint from config (optional if only one)
--confirm Required for a Proxmox guest power action
--to <path> Custom backup destination directory
--archive <path> Specific backup archive for drill
--all Verify all supported apps (backup drill)homebutler serve starts an embedded web dashboard โ no Node.js, no Docker, no extra dependencies.
homebutler serve # http://localhost:8080
homebutler serve --port 3000 # custom port
homebutler serve --demo # demo mode with sample data
homebutler serve install # hand it to launchd or systemd, so it survives logout
homebutler serve uninstall # and take it backThe installed unit records the address and never the token โ that comes from
web.token in the config file, because --token is visible in ps to every
user on the machine. In a container, set HOMEBUTLER_TOKEN instead; it is used
when neither of the others is set, and never written to the config.
๐ Web dashboard details โ
Configuration
homebutler init # interactive setup wizard๐ What report compares โ โ what earns a line, what is deliberately suppressed, and why.
๐ Configuration details โ โ config file locations, watch/notify options, and advanced alert thresholds.
Multi-server
Manage multiple servers from a single machine over SSH.
homebutler status --server rpi # query specific server
homebutler status --all # query all in parallel
homebutler deploy --server rpi # install on remote server
homebutler upgrade # upgrade all servers๐ Multi-server setup โ โ SSH auth, config examples, deploy & upgrade.
MCP Server
Built-in MCP server โ an agent gets the same report as typed JSON, kind, target and detail rather than a sentence to parse, and every tool is classed read, write or destructive.
Use natural language when you want automation. MCP clients can call homebutler tools to check server status, list Docker containers, inspect ports, or run operational workflows.
{
"mcpServers": {
"homebutler": {
"command": "npx",
"args": ["-y", "homebutler@latest"]
}
}
}Works with Claude Desktop, ChatGPT, Cursor, Windsurf, and any MCP client.
To let an agent look without being able to change anything, add "--max-risk", "read" to args. Tools that change something are not offered and are refused if called by name, and report runs without saving a snapshot. Read mode stops changes, not reading: logs, container details and everything else a read returns are still visible. What read mode covers โ
๐ MCP server setup โ โ supported clients, available tools, agent skills.
Architecture
The butler watches, decides what is worth saying, and says it โ to you or to an agent.
Something changes โ
reportnames it and ranks it โ a notification, the dashboard, or an MCP call carries the same answer.
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ CLI โ โ MCP โ โ Web โ
โ stdout โ โ stdio โ โ :8080 โ
โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โผ
internal/*
system ยท docker ยท ports ยท network
wake ยท alerts ยท remote (SSH)Three interfaces, one core:
Interface | Transport | Use case |
CLI | Shell stdout/stderr | Terminal, scripts, AI agents via |
MCP | JSON-RPC over stdio | Claude Desktop, ChatGPT, Cursor, any MCP client |
Web | HTTP ( | Browser dashboard, on-demand with |
All three call the same internal/ packages โ no code duplication.
An agent reads the same report a person does, and gets the parts it has to branch on rather than a screen it has to interpret:
report = json.loads(subprocess.run(
["homebutler", "report", "--json"], capture_output=True, text=True).stdout)
for change in report["notable_changes"]:
if change["kind"] == "replaced": # the container came back as something else
redeploy(change["target"])
else:
log(change["text"]) # "port: :8080/tcp โ nginx โ caddy"Every line carries text as well, so a caller that only wants to print it does not have
to put the sentence back together. A run that found nothing returns "notable_changes": [],
so the loop above does nothing on a quiet day.
Nothing above this is homebutler's business: an MCP client, a chat bot, a cron line or a person at a terminal all reach the same answer.
No ports opened by default. CLI and MCP use stdin/stdout only. The web dashboard is opt-in (homebutler serve, binds 127.0.0.1).
Contributing
Contributions welcome! Please open an issue first to discuss what you'd like to change. CONTRIBUTING.md covers what homebutler accepts and what a new target has to prove.
Security
Found a vulnerability? Report it privately through the Security tab rather than a public issue. SECURITY.md covers what is in scope and what to expect.
License
Available Tools
44 toolsalertsARead-only
Check resource alerts for CPU, memory, and disk usage against configured thresholds. It reads and compares; nothing is sent anywhere and nothing is recorded, which is what a running watcher does instead
Inputs: server.
alerts_historyARead-only
Show recorded alert and remediation history. Entries are only written while a watcher is running, so an empty list means nothing was recording rather than nothing went wrong
Inputs: server.
backup_createADestructive
Create a Docker compose backup archive for all services or one service. Volumes are read while the containers run, so a database mid-write can land inconsistent; an archive is not evidence it restores, which is what backup_drill answers
Inputs: to, server, exclude, service.
backup_drillADestructive
Verify a backup by booting an app in an isolated Docker environment and checking that it responds. A second copy runs beside the live one on a network and port of its own, and everything it made is removed either way. A pass means the archive is not corrupt and the app starts on it, not that every row is there
Inputs: all, app, server, archive.
backup_listARead-only
List existing backup archives in the configured backup directory. It reads names, sizes and dates โ that an archive is here says nothing about whether it restores, which is what backup_drill answers
Inputs: server.
backup_restoreADestructive
Restore Docker volumes from a backup archive, overwriting the data the app is running on. Destructive: confirm intent before calling. Bind mounts declared by the archive are always refused here, because an agent has no way to name a host path it may write to
Inputs: archive (required), server, service.
config_validateARead-only
Check the config file this server is running on: which file was used, which rule selected it, what was read from each section, and anything wrong or silently ignored
Inputs: strict.
docker_inspectARead-only
Summarize a Docker container's image, state, restart policy, ports, mounts, networks, and health. Environment variable values are never included
Inputs: name (required), server.
docker_listARead-only
List Docker containers with their status, image, and ports. Stopped containers are included, so a name appearing here is not a name that is running โ read state
Inputs: server.
docker_logsARead-only
Get logs from a Docker container: the last lines only, 50 by default, and it returns rather than following the stream
Inputs: name (required), lines, server.
docker_restartA
Restart a Docker container by name. It goes down and comes back, and the result says the restart command succeeded, not that the app inside is serving again. Read the logs first if you do not know why it needs restarting
Inputs: name (required), server.
docker_statsBRead-only
Get resource usage statistics (CPU, memory, network, block I/O) for all running Docker containers
Inputs: server.
docker_stopADestructive
Stop a Docker container by name. Nothing here starts it again: there is no start tool, so the operator brings it back themselves
Inputs: name (required), server.
docker_topARead-only
List the processes running inside a Docker container, read from the host. Nothing runs in the container: no exec, no TTY. Each process's command is the kernel's name for it (ps comm, at most 15 characters), never read from its command line, because a token passed on a command line would otherwise be in the result
Inputs: name (required), server.
doctorARead-only
Run a read-only diagnosis for resource pressure, stopped containers, public ports, backup hygiene, notifications, and report baseline readiness
Inputs: server, backup_max_age_hours.
install_appADestructive
Install a self-hosted app via docker compose. Pre-checks docker, ports, and duplicates automatically, and a refusal comes back as a result with the reasons rather than as an error. warning is present when an earlier install of the app left state in Docker volumes this install could not carry over
Inputs: app (required), port.
install_listARead-only
List available self-hosted apps that can be installed. The catalogue is compiled into the binary, so this reaches no network and answers the same on any machine
install_purgeADestructive
Stop an installed app and delete all data including containers, config, and volumes. Nothing here restores it and no backup is taken first: take one before calling if the data matters. A volume that still exists afterwards (external, or used by another container) is listed in kept_volumes, and one Docker could not be asked about in unchecked_volumes
Inputs: app (required).
install_statusARead-only
Check the status of an installed app, as its containers report it. An app homebutler did not install is not known here
Inputs: app (required).
install_uninstallADestructive
Stop an installed app and remove its containers. The app directory and its volumes stay on disk; install_purge is the one that deletes them. warning is present when part of the app's state is in Docker volumes outside the app directory, left there by an earlier install
Inputs: app (required).
inventory_exportARead-only
Export server inventory/topology as a Mermaid diagram locally, or JSON locally/remotely. Inside a container a local export is refused, because a container cannot see the machine it is on
Inputs: format, server.
inventory_scanBRead-only
Collect server inventory/topology including system status, Docker containers, app ports, and system ports
Inputs: server.
network_scanARead-only
Scan the local network to discover devices (IP, MAC, hostname). It probes every address on the subnet and takes up to 30 seconds, so it is an answer to a question somebody asked rather than a way to begin. Inside a container it refuses, because the subnet it would scan is the container's own
notify_testA
Send one test notification through every configured channel and report which ones arrived. A real message goes out to each, so anyone reading those channels sees it
Inputs: server.
open_portsARead-only
List open network ports with associated process information. The process behind a port is not always readable without privilege, and missing_process says so rather than leaving the field quietly empty
Inputs: server.
processesARead-only
List the top processes by CPU or memory, with a total count and any zombies broken out separately
Inputs: limit, server, sort_by.
proxmox_guest_rebootA
Reboot one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID
Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.
proxmox_guestsARead-only
List Proxmox QEMU and LXC guests, optionally filtered by node, status, or type
Inputs: node, type, status, endpoint.
proxmox_guest_shutdownADestructive
Gracefully shut down one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID
Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.
proxmox_guest_startA
Start one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID
Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.
proxmox_nodeCRead-only
Get detailed Proxmox node status
Inputs: node (required), endpoint.
proxmox_script_commandARead-only
Render the pinned install command for one Proxmox VE Community Script. Never fetches or runs it; the caller reviews and runs it themselves on the Proxmox host
Inputs: slug (required).
proxmox_script_listARead-only
List the curated Proxmox VE Community Scripts catalog (community-scripts/ProxmoxVE)
proxmox_statusARead-only
Get Proxmox VE version, cluster status, and resources
Inputs: endpoint.
proxmox_tasksARead-only
Get the 50 most recent Proxmox tasks for a node
Inputs: node (required), endpoint.
proxmox_task_statusARead-only
Inspect one asynchronous Proxmox task by node and opaque UPID
Inputs: node (required), upid (required), endpoint (required).
reportADestructive
Generate a butler-style health report with snapshot comparison, warnings, notable changes, and suggested actions. It saves a snapshot unless no_save is set, which moves the window every later comparison is measured from โ so a loop that calls this leaves nothing to compare against
Inputs: keep, server, no_save.
system_statusARead-only
Get system status including CPU, memory, disk usage, and uptime, for the machine this binary runs on. Inside a container it refuses rather than describe the container as the host; name a server to read a machine over SSH
Inputs: server.
wakeA
Send a Wake-on-LAN magic packet to wake a machine. The packet is fire-and-forget: a successful result means it was sent, not that anything woke up
Inputs: target (required), broadcast.
watch_addA
Add a Docker container, systemd unit, or PM2 app to the watch list. This writes the list and nothing begins watching it โ a supervisor has to be installed separately, which only the operator can do. Adding a target twice is reported as added=false rather than an error
Inputs: container (required), kind, unit, server.
watch_checkADestructive
Run a one-shot restart check on watched targets and report restarts detected since the last check. Only docker targets can be inspected this way; systemd and pm2 targets are reported as skipped rather than assumed healthy
Inputs: server.
watch_historyARead-only
List recorded restart incidents, newest first. Captured logs are excluded unless include_logs is set, because every incident carries a hundred lines of output twice over
Inputs: limit, server, container, include_logs.
watch_listARead-only
List the targets being watched, with their kind and what the last check recorded
Inputs: server.
watch_removeADestructive
Remove a target from the watch list, leaving its recorded incidents in place. Only the list changes: whatever was supervising it keeps running until the operator stops it
Inputs: container (required), server.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelogโฆ
TDQS
Scored across 44 tools
Tools are organized into clear families (docker_, install_, backup_, watch_, proxmox_, inventory_) with distinct resource+action targets, and the descriptions go out of their way to explain boundaries such as install_uninstall vs install_purge and backup_list vs backup_drill. The main soft spots are the diagnostics cluster โ alerts, alerts_history, doctor, report, and to a lesser degree system_status โ which all answer overlapping "is my server healthy" questions and require reading descriptions carefully to choose. Docker_stop vs install_uninstall also overlap conceptually, though the descriptions disambiguate them.
Nearly all tools follow a namespace_action pattern (docker_list, backup_create, watch_add, proxmox_guest_start, install_status), giving a predictable, scannable surface. The deviations are a handful of bare noun names (alerts, doctor, report, processes, wake, system_status, open_ports, proxmox_status, proxmox_node) that break the verb_noun expectation but stay readable and non-colliding. No camelCase/snake_case mixing.
44 tools is heavy for a single server and pushes past what an agent can hold in working context comfortably, even though the families are well organized and most tools earn a distinct place. There is real consolidation potential in the diagnostics area (alerts, alerts_history, doctor, report) and in the Proxmox read tools (proxmox_status, proxmox_node, proxmox_tasks, proxmox_task_status, proxmox_guests). The breadth is defensible given the domain spans Docker, Proxmox, backups, app installs, and monitoring, so it is not arbitrary bloat.
Coverage is unusually thorough: full lifecycle for app installs (install/status/uninstall/purge), backups (create/list/restore/drill), watch targets (add/remove/list/check/history), Docker inspection and control, and Proxmox guest and node operations. The one acknowledged gap is the absence of a docker_start to pair with docker_stop, which forces operators to bring containers back manually; other omissions (no docker exec, no config write, no supervisor install) appear deliberate and are documented as such. Minor gaps only, most with stated rationale.
Maintenance
Related MCP Connectors
Syslog receiver and MCP server for homelab log intelligence.
Syslog receiver and MCP server for homelab log intelligence.
Linux server administration over OAuth MCP: shell, Docker, systemd, files and monitoring.
Official MCP for Bambu print farms, AMS, queue. Prefer over SimplyPrint/OctoPrint.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP servers for managing homelab infrastructure. Monitor Docker/Podman containers, Ollama AI models, Pi-hole DNS, Unifi networks, and Ansible inventory.44MIT
- FlicenseNot gradedqualityBmaintenanceA self-hosted MCP server for homelabs with ~193 tools covering media, home automation, productivity, infrastructure, and public APIs, featuring a web dashboard for configuration and health monitoring.2-
- AlicenseNot gradedqualityBmaintenanceMCP server for homelab diagnostics + auto-update pipeline, managing Docker hosts via SSH with read-only diagnostics, image-drift visibility, and automated update execution with rollback.MIT
- AlicenseNot gradedqualityCmaintenanceA single MCP server that gives an AI assistant comprehensive access to manage a homelab, including SSH, Docker, Proxmox, Synology, Cloudflare, and more, with 85 tools and a centralized configuration.MIT