Automate your homelab with Claude Code
An agent with access to your server can save you hours of maintenance, or wipe your data. Here's how I set mine up so only the first one happens.
I run five machines in my homelab and none of them maintain themselves. Updating packages, working out why a container keeps restarting, checking last night's backup actually exists. Nothing hard, but it's half an hour a week that disappears without you noticing.
For a few months now an agent has been doing that work. Not in the reckless way you're picturing: with scoped permissions, no access to anything it shouldn't touch, and a human approving whatever matters.
First question: where does the agent live
Not on every machine. I have a dedicated LXC container — the same approach as isolating with Docker, but at machine level — and it's the only one holding SSH keys to the rest:
pct create 140 local:vztmpl/debian-12-standard_amd64.tar.zst \
--hostname agent \
--memory 2048 \
--cores 2 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1The key parts are --unprivileged 1 and the fact that this container has no
access to the NAS storage or to the Proxmox management interface. If someone
compromises the agent, they've reached a machine that only knows how to talk
SSH to others. That's bad, but it's recoverable.
SSH keys are the real boundary
This is where most people get relaxed, and where what can actually happen gets
decided. Every destination has its own key, restricted by command in the target
server's ~/.ssh/authorized_keys:
command="/usr/local/bin/agent-readonly",no-port-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA...And agent-readonly is a script that only lets through what I've decided:
#!/usr/bin/env bash
set -euo pipefail
case "${SSH_ORIGINAL_COMMAND:-}" in
"docker ps"*|"docker logs"*|"systemctl status"*|"df -h"|"journalctl"*)
exec bash -c "$SSH_ORIGINAL_COMMAND"
;;
*)
echo "Command not allowed: ${SSH_ORIGINAL_COMMAND:-empty}" >&2
exit 1
;;
esacWith this the agent can diagnose anything and can't break anything. It's the difference between handing someone your house keys and letting them look through the window.
What I actually ask it for
The Monday review. A cron job runs the agent with a fixed brief: look at container status, free space and the last 24 hours of logs, and tell me only what's out of the ordinary. No three-page reports: if everything's fine, I want one paragraph.
claude -p "Review the homelab following runbooks/weekly-review.md \
and write the result to reports/$(date +%F).md" \
--allowedTools "Bash(ssh:*)" "Read" "Write"Those --allowedTools aren't decoration. Without them the agent picks its own
tools, and I don't want to find out one day that editing a config file seemed
like a good idea.
Translating an error into plain language. When something fails I hand it the
log and it tells me what happened. An exit code 137 is a process the kernel
killed for eating too much memory, but you have to know that. The agent knows,
and it also checks how much RAM that machine was allocated.
Writing the docker-compose.yml for a new service. I give it the official
docs and my conventions — network, Traefik labels, where volumes live — and it
hands back a file that fits the rest. I always review it, but it saves me
copy-pasting from documentation.
Documenting what already exists. This one surprised me. I asked it to walk the machines and write an inventory. Two services turned up that had been running for a year and that I'd completely forgotten about.
What I never let it do
Apply updates on its own. It can tell me what's pending and what each
version changes. I run the apt upgrade myself, after a snapshot.
Touch the reverse proxy. It's the only entry point from the internet. A mistake there doesn't take down one service: it takes down all of them and locks you out on the way.
Delete anything. Not old logs, not unused Docker images, not expired backups. Cleanup is done by dumb scripts with fixed rules, which is exactly what you want for anything irreversible.
Work without a snapshot. Before any session where it'll write, I snapshot the machine involved. It takes fifteen seconds.
The mistake that changed my approach
Early on I gave it broad access to a test machine. I asked it to free up space and it did: it deleted the unused Docker images. Including the base image I'd kept precisely because it was no longer in the public registry.
I lost nothing important and spent an afternoon rebuilding it. But I learned what really matters: the agent can't tell "unused" from "not used right now". That context is yours and only yours, so irreversible decisions stay on your side of the line.
Worth it, with caveats
I've gone from half an hour of maintenance a week to about ten minutes reading reports. The agent hasn't fixed anything on its own — I don't let it — but it tells me where to look, and that's ninety per cent of the job.
If you're thinking of setting this up, start the opposite way to how I did: give it read-only access and ask it to tell you things. After a month of reading what it writes, you'll know exactly what you can trust it with and what you can't.