AI Agents in My Hosting Workflow
My homelab is mostly text: Docker Swarm compose files, bootc Containerfiles, Forgejo workflows, a few Ansible playbooks and this Hugo blog. All of it lives in git, and for years the workflow has been the same: edit in an editor, render, deploy, check the logs, repeat.
Over the last months two coding agents moved into that loop: opencode and Claude Code. This post covers how I set them up, what they do for me, and where I still keep my hands on the wheel.
What got me started was a simple realization: my whole environment is already code. Every stack, every image, every pipeline is a text file in a repo, with a clear structure and a way to validate it. That is exactly the kind of setup coding agents are built for – so I started playing with them on my own infrastructure, and they stayed.
Two Tools, Two Roles
I didn’t pick one agent. I use both, because they cover different needs.
opencode is the open one. It speaks to any OpenAI-compatible endpoint, so I
can point it at models I run myself – why I do that is covered in
the previous post. My
opencode.json defines a few providers:
- Ollama on a machine in my LAN, serving small models like
qwen3.5:9b,gemma4orgpt-oss:20bwith 16k–32k context windows - Hetzner inference for larger open-weight models (Qwen3.6 35B A3B, Qwen3.8 27B) with 256k context, when the local box runs out of breath
{
"$schema": "https://opencode.ai/config.json",
"model": "ollama/qwen3.5:9b-16k",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama",
"options": { "baseURL": "http://<lan-host>:11435/v1" },
"models": {
"qwen3.5:9b-16k": {
"name": "qwen3.5:9b-16k",
"limit": { "context": 16384, "output": 4096 }
}
}
}
}
}
Setting the limit per model matters: without it opencode doesn’t know how
small the context of a local model really is, and long sessions just fall apart.
Claude Code is the heavy lifter. When a task spans many files, needs planning, or I want a second opinion before a deploy, I reach for it.
In practice the split looks like this:
- Claude Code writes most of the stacks. New compose files, refactors, CI pipelines – the generation work, where model quality matters most and the input is my (secret-free) repo.
- opencode is my test bench. I use it to try out models and prompts and to see how far the open-weight models get on the same tasks.
- opencode sets up production. Rolling a stack out to the productive
environment – including the
.envfiles and the Docker Swarm secrets – happens with opencode on a local model. That is the part where real credentials are in play, so it runs on hardware where nothing leaves my network.
Teaching the Agent My Conventions
The single most useful thing I did was writing a CLAUDE.md (opencode reads
AGENTS.md) in each infrastructure repo. It is plain markdown that tells the
agent what the repo is and – more importantly – the rules that are only in my
head.
For the Swarm repo that means things like:
- One
compose_<service>.yamlper stack, with a.envfile next to it that is gitignored and never invented by the agent – it has to ask - The deploy pipeline and why it looks the way it does:
docker compose -f swarm/compose_<name>.yaml --env-file swarm/compose_<name>.env config \
| grep -v '^name' \
| sed -e 's/published: "\([0-9]*\)"/published: \1/' \
| sudo docker stack deploy -c - <name>
- Traefik routing goes into
deploy.labels, never top-levellabels: - Docker API access only through a
socket_proxysidecar, never by mounting/var/run/docker.sockdirectly - Automation labels (
shepherd.autodeploy,diun.enable,sablier.enable) are set deliberately, not copy-pasted from the last stack - Images are always pinned to a fully-qualified registry reference
Without that file, an agent produces a plausible compose file. With it, it produces one that fits into my cluster. The difference is the same as between a new colleague on day one and one who has read the wiki.
The gorgeOS repo gets the same treatment: which submodule holds what, how the
image stack depends on itself, and that every Containerfile ends with
bootc container lint.
Giving It Eyes: MCP Servers and Remote Sessions
Reading and writing files is only half of hosting. The other half is looking at what is actually running. For that I hooked a few MCP servers into the agents:
- Beszel – system and container metrics, alert history. “Why was the NAS slow last night?” becomes a question the agent can answer with data instead of guesses.
- Portainer – read the state of stacks and services on the Swarm.
- n8n – inspect and edit automation workflows.
Where there is no MCP server, I simply run the agent on the host itself, so it can read the journal, the unit files and the system state directly.
This turned out to be the biggest time saver of all. With access to the running systems, the agent finds the small problems I would never have tracked down myself – or never even looked for. Two examples:
- Home Assistant noise. My Home Assistant logs had been full of errors for a while. Everything worked, and it wasn’t obvious where they came from, so I ignored them. The agent went through the logs, traced each error back to its cause and fixed all of them.
- bootc systems that didn’t boot reliably. For some time, my bootc machines
had trouble booting every now and then. I started an agent directly on one of
the affected hosts, and it found the cause: a race
condition between the network coming up and a systemd
.mount/.automountunit with the wrong dependencies. Sometimes the mount won, sometimes the network did.
Neither problem was hard once found. Finding them was the hard part – and that is exactly where an agent that can read logs and state is better than me: it doesn’t get bored after the fiftieth log line.
Where I Keep the Wheel
Agents are fast, and fast is dangerous on infrastructure. My rules:
- Everything goes through git. The agent edits files; I review the diff and commit. Nothing is deployed from an agent session I didn’t read.
- Secrets stay local.
.envfiles and Swarm secrets are never part of a Claude Code session – theCLAUDE.mdsays so explicitly. Only opencode on a local model touches them, and even there the values come from me; they are never generated by AI. - Validate before deploy.
docker compose ... configrenders the stack without touching the cluster – that is the agent’s “test suite”. - Read-only first. MCP access to monitoring is harmless; write access to running systems is something I grant per task, not by default.
Results So Far
- New stacks are a conversation now. Adding a service used to mean copying an existing stack, reading through the app’s example compose file, writing the environment file and setting up a new database. Today I describe what I want and review what comes back.
- Debugging got thorough. Problems I had learned to live with – noisy logs, the occasional failed boot – finally got fixed, because looking into them no longer costs me an evening.
- Local models carry more than I expected. Small local models are good enough for many tasks, but the bigger ones get me to a good result faster.
What’s Next
I’m far from done. I will keep exploring where coding agents can improve my workflows – and there is plenty of room: many of the apps in my stack already ship their own MCP servers, and every one of them is another system the agent can look at instead of me.
Comments
Talk about this post on Mastodon or Bluesky with #m1ch20260928AiAgentsInMyHostingWorkflow.
To comment here, reply to the post on Mastodon or on Bluesky.
Loading comments contacts graz.social or mastodon.social (wherever the post is) and Bluesky, see the privacy policy.