浪人
DE | EN
Claude Code as Homelab Assistant - Part 1: Setup & First Audit
tech

Claude Code as Homelab Assistant - Part 1: Setup & First Audit

Back to Blog
6 min read

Claude Code as Homelab Assistant - Part 1: Setup & First Audit

Introduction

Anyone running a homelab knows the pattern: you have an idea, open a chat with Claude, explain the context, get a suggestion, copy it into the terminal, run it, report the result back, get a correction - and so on. It works, but it’s cumbersome. Claude doesn’t know your server, you have to explain what’s running each time, and between planning and execution you’re always the manual middleman.

Claude Code does things differently. It runs locally on your machine, connects directly to your server via SSH, reads configs, executes commands, and waits for your confirmation before making any changes. No copying, no explaining, no manual back-and-forth.

For me there’s an additional twist: I traditionally work on multiple systems simultaneously - desktop PC, laptop, and tablet. Right now I’m also experimenting with switching to Linux, so I’m working in parallel on Windows and Linux. The setup needs to work on all systems without having to rebuild it each time.

In this series, I’ll show how I set up Claude Code as an assistant for my homelab - with reusable prompts, automated tests, and a setup that works on any machine in just a few minutes.

Part 1: Concept, repo structure, SSH access setup, first security audit. Part 2: Fix an issue found in the audit. Part 3: Deploy a new Docker service - from zero to HTTPS.


Main Section

The Concept

The basic idea is simple: a private GitHub repo that serves as the “brain” for Claude Code. It contains the context about the server, reusable prompts as slash commands, and setup scripts that work on every machine - Windows, Linux, macOS.

homelab-claude/
├── CLAUDE.md                      <- Context that Claude Code reads automatically
├── setup.sh                       <- One-time setup Linux / macOS
├── setup.ps1                      <- One-time setup Windows
├── .env.example                   <- Required secrets (template)
├── .claude/
│   └── commands/
│       ├── health-check.md        <- /health-check
│       ├── security-audit.md      <- /security-audit
│       └── deploy-service.md      <- /deploy-service
└── prompts/                       <- Readable reference

screen1-Repo

The centerpiece is CLAUDE.md - a file that Claude Code reads automatically on every startup. It contains everything the assistant needs to know about the server: IPs, services, URLs, critical containers that must never be touched without confirmation, conventions for new stacks, and behavioral rules.

## Critical Services -- ALWAYS HANDLE LAST

| Service       | Container      | Why critical                                 |
|---------------|----------------|----------------------------------------------|
| Traefik       | traefik        | Reverse proxy - all HTTPS routes             |
| AdGuardHome   | adguardhome    | DNS - if this goes down, network is offline  |
| Tailscale     | tailscale      | VPN - only external access                   |

screen2-claudeMD

Important: The CLAUDE.md in this repo is my personal configuration - specific to my setup with my services, IPs, and conventions. Anyone implementing this needs to adapt this file for their own homelab. The repo is a starting point, not a finished product you just clone and go.

I’m also considering adding a “discovery” prompt: Claude Code connects to an unknown server, reads docker ps, networks, volumes, and existing configs - and automatically generates an initial CLAUDE.md from that. That would be the clean entry point for anyone starting from zero. If I implement it, it will come as its own part in the series.

No more explaining the context in every chat. The context is documented once cleanly and always available - on every machine, after every git pull.

Setting Up SSH Keys

Claude Code connects to the server via ssh-mcp - an MCP server that executes SSH commands. There’s one important limitation: the SSH key must not have a passphrase. ssh-mcp cannot use encrypted keys.

For an internal homelab that’s only reachable via LAN or Tailscale, this is acceptable. I recommend a dedicated key just for Claude Code - that way it can be revoked easily if needed without touching the main key.

Windows (PowerShell):

# Create key -- leave passphrase empty
ssh-keygen -t ed25519 -C "homelab-claude" -f "$env:USERPROFILE\.ssh\id_ed25519_homelab"

# Transfer to server
type "$env:USERPROFILE\.ssh\id_ed25519_homelab.pub" | ssh ronin@192.168.178.22 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# Test
ssh -i "$env:USERPROFILE\.ssh\id_ed25519_homelab" ronin@192.168.178.22

Linux / macOS:

# Create key -- leave passphrase empty
ssh-keygen -t ed25519 -C "homelab-claude" -f ~/.ssh/id_ed25519_homelab

# Transfer to server
ssh-copy-id -i ~/.ssh/id_ed25519_homelab.pub ronin@192.168.178.22

# Test
ssh -i ~/.ssh/id_ed25519_homelab ronin@192.168.178.22

Once the connection works without a password, everything is ready. Add to .env:

SSH_KEY_PATH=~/.ssh/id_ed25519_homelab

Gotcha: On my first attempt, I used my existing SSH key - which had a passphrase. Claude Code started up, every SSH command failed with Cannot parse privateKey: Encrypted private OpenSSH key detected. Solution: create a new key without passphrase, re-register MCPs. Takes 5 minutes, but you should do it right from the start.

screen3-claude

Setup on a New Machine

This is exactly where the repo principle pays off. Whether Windows, Linux, or macOS - the setup is always the same:

# Clone repo
git clone git@github.com:RoninRage/homelab-claude.git
cd homelab-claude

# Configure secrets
cp .env.example .env
# Enter SSH_HOST, SSH_USER, SSH_KEY_PATH

Then depending on your system:

# Linux / macOS
./setup.sh

# Windows
powershell -ExecutionPolicy Bypass -File setup.ps1

The script installs Claude Code, registers three MCP servers (SSH LAN, SSH Tailscale, Playwright), and shows which servers are active at the end. Since I’m switching between Windows and Linux, it was important to me that both scripts work identically - same output, same error messages, same order. No difference except the filename.

Then start claude once - the browser opens for Anthropic login, once per machine. After that everything runs.

screen4-slashcommands

Slash Commands

Instead of copying prompts from files and pasting them, all prompts are available as slash commands directly in Claude Code. The files are in .claude/commands/ - Claude Code recognizes this folder automatically.

/health-check      <- Container status, memory, backup log
/security-audit    <- Comprehensive security audit
/deploy-service    <- Deploy new Docker service

The same files are also in prompts/ as readable reference - to check what a command actually does without having to open Claude Code.

screen5-Passphrase

The First Security Audit

/security-audit

Claude Code connects via SSH and checks five areas in parallel:

  1. Traefik configuration - which services run without auth middleware?
  2. Open ports - what listens on all interfaces, not just via Traefik?
  3. Container security - is anything running privileged or as root?
  4. Image hygiene - which images use :latest instead of a fixed tag? Which are outdated?
  5. Backup coverage - which services have volumes that aren’t backed up?

screen6-sudo

The result appears structured in the terminal with a prioritized to-do list. Nothing gets saved or committed - you decide what happens with it.

Important: Claude Code only gets read access during audits. No files are written, no commands are executed that change anything. Raw data like IPs or internal ports don’t end up in files or repos - only the processed summary.

Gotcha #2: The audit prompt uses ss -tlnp to check for open ports. On the first run, it threw an error: sudo: a password is required - ssh-mcp can’t pass a sudo password. The solution is simple: ss -tlnp also works without sudo, you just don’t see process names. For a port audit that’s completely sufficient. The prompt was adjusted accordingly.

screen7-auditrunning

screen8-auditreport


Summary

What used to be manual back-and-forth is now a single command. Claude Code reads the context, connects to the server, runs the checks, and delivers a structured report - without having to explain the setup every time.

The repo principle has a nice side effect: the context is versioned, available on every machine, and grows with the homelab. When a new service is added, CLAUDE.md gets updated - and all machines know about it on the next git pull. For someone working on multiple systems and switching between operating systems, this is not a nice-to-have, it’s the actual reason the setup is built this way.

In Part 2, I’ll take a closer look at one of the findings from the audit and fix it - directly with Claude Code, step by step with confirmation.

One final thought: what’s executed manually via slash command here can in principle be automated. /health-check and /security-audit could run as regular cronjobs - an automatic status report every morning, an audit every week. The prompts are just the starting point: what to check, how the report looks, and what happens on a critical finding - all of that can be adapted to your own requirements. Anyone who wants can build a complete monitoring system from this, anyone who prefers manual control can stick with slash commands. Both work with the same base.

The repo: github.com/RoninRage/homelab-claude-public

Note about the repo: This is an anonymized example of my personal setup - IPs, username, domain, and internal paths are replaced with placeholders. The CLAUDE.md reflects my homelab and is not a finished template you can just take over. Anyone implementing this needs to create their own CLAUDE.md - either manually, or with AI help: just provide docker ps, docker network ls, and existing compose files as context and have Claude generate an initial CLAUDE.md. That’s done in a few minutes and immediately gives you a solid foundation.