This is Part 1 of the Building a Homelab with AI series. Next: Why Infrastructure as Code?
The Force Multiplier
I have been a software engineer and Linux administrator for 26 years. I have shipped production code, managed fleets of servers, debugged kernel panics at 3 AM, and written more bash one-liners than I care to admit. I am not a beginner. But when I started using Claude Code — Anthropic’s CLI tool that puts Claude directly in your terminal — something shifted. I went from being good at a few things and passable at many to being genuinely dangerous across the entire stack.
This is not a story about AI replacing engineers. It is a story about what happens when you pair deep domain experience with a tool that can generate, debug, explain, and iterate on code at the speed of conversation. The result is not “AI did my job.” The result is “I did ten times more of my job in a fraction of the time.”
In one session, I went from an empty Git repository to a fully codified homelab: Ansible playbooks bootstrapping a fleet of LXC containers, Docker Compose stacks auto-deploying via Portainer GitOps, a reverse proxy migrated to an isolated DMZ, a security review of everything we built, and — yes — blog posts documenting the entire journey. Every commit in the repo includes Co-Authored-By: Claude Opus 4.6 <[email protected]> because transparency matters.
This post is about the art of the possible, the craft of steering it, and the discipline of using it well.
The Art of the Possible
Here is what becomes achievable when you pair a homelab enthusiast with a capable LLM running in the terminal:
Infrastructure as Code from scratch. I had a homelab with a dozen services running across LXC containers and VMs. All manually configured. All tribal knowledge. In one session, Claude Code helped me design the repo structure, write Ansible playbooks, debug the chicken-and-egg bootstrap problem (three failed approaches before landing on pct exec through Proxmox), and roll out consistent configuration across every host. The IaC series covers this in detail.
Security hardening with fresh eyes. I asked Claude to review the entire IaC repo for security issues. It found things I had walked right past: a vault password file missing from .gitignore, an application binding to 0.0.0.0 and bypassing the reverse proxy. When you are too close to the code, a second set of eyes — even artificial ones — catches what you miss. The security review details every finding.
Architecture decisions with trade-off analysis. Should the DMZ reverse proxy be Alpine or Ubuntu? LXC or VM? Docker container or bare binary? Claude did not just give me an answer — it laid out the trade-offs and let me make the call. I chose Ubuntu (consistency), VM (kernel isolation), and bare binary (minimal attack surface). The reasoning is in the Caddy migration post.
Documentation that actually gets written. You are reading AI-assisted documentation right now. The single biggest barrier to documentation is not technical skill — it is activation energy. When you can say “write a blog post about what we just did” and get a solid first draft in sixty seconds, documentation goes from “I should do this someday” to “it is already done.” Every post in this series was drafted by Claude and refined by me.
Learning new tools in context. I do not write Ansible playbooks every day. I know the concepts, but I would normally spend an hour reading docs to remember module syntax. With Claude Code, I describe what I want in plain English and get a working playbook with explanations of every module option. When it fails, I paste the error and get a fix. The feedback loop is minutes instead of hours.
Prompt Engineering: The Steering Wheel
Here is the thing nobody tells you about working with AI: the quality of the output is directly proportional to the quality of the input. Prompts are the steering wheel. If you give vague direction, you get generic output. If you give precise context, constraints, and goals, you get output that is genuinely useful.
Let me show you the difference with real examples from this project.
Bad Prompt
Set up monitoring for my homelab.
This will get you a generic Prometheus tutorial. Claude does not know your hosts, your network topology, what services you run, or what you care about monitoring. It will hallucinate a reasonable-looking setup that does not fit your environment.
Good Prompt
I have a homelab with the following hosts: pve (Proxmox, 10.0.0.4), docker-host (Docker host, 10.0.0.12), dev (Ansible control node, 10.0.0.13), and several LXC service containers. I want to deploy Prometheus with node_exporter on every host, with Grafana dashboards for CPU, memory, disk, and network. Prometheus and Grafana should run as a Docker Compose stack on docker-host. Node_exporter should be deployed via Ansible. I use Portainer GitOps for stack deployment and Ansible Vault for secrets. Write the Docker Compose file for the monitoring stack and the Ansible role for node_exporter.
This gives Claude everything it needs: the inventory, the architecture, the deployment mechanism, the secrets strategy. The output will be specific to your environment and integrate with your existing tooling.
The Prompt Engineering Principles
1. Set the context first. Before asking Claude to do anything, tell it what it is working with. Your CLAUDE.md file is the first line of defense here — it loads automatically and gives Claude persistent context about your project. But for specific tasks, layer on additional context: what exists, what you are trying to achieve, what constraints apply.
2. Be specific about outputs. “Write a playbook” is worse than “write an Ansible playbook that deploys node_exporter as a systemd service on all hosts in the linux group, using the ansible.builtin.get_url module to download the binary, with a handler to restart the service when the config changes.”
3. Include constraints. “Do not use Docker for this — deploy it as a native systemd service.” “Bind to 127.0.0.1 only, not 0.0.0.0.” “Use Ansible Vault for the API token, not a plaintext variable.” Constraints prevent Claude from taking the path of least resistance when that path is wrong for your environment.
4. Iterate, do not start over. If the first output is 80% right, do not re-prompt from scratch. Say “that is close, but change X and Y.” Claude maintains conversation context and will refine incrementally. Each iteration gets closer to what you need.
5. Ask for reasoning. “Why did you choose ansible.builtin.copy instead of ansible.builtin.template here?” Forces Claude to explain trade-offs, which helps you learn and also catches cases where the reasoning is flawed.
Real Workflow Examples
Infrastructure as Code
The entire IaC series was built in conversation with Claude Code. Here is what that workflow looked like in practice:
Me: “I want to set up an Ansible inventory for my homelab. Here are the hosts…” (provides IP addresses, roles, SSH configuration)
Claude: Generates hosts.yml with proper group structure, ansible.cfg with sensible defaults, and a group_vars/all.yml with common variables.
Me: Runs ansible all -m ping. Three hosts fail.
Me: “These three containers do not have sudo installed and root SSH is disabled. How do I bootstrap them?”
Claude: Suggests -u root approach. Fails because of Ansible variable precedence. Suggests -e ansible_user=root. Partially works but hits the sudo-not-installed wall. Then I step back and say…
Me: “I have SSH access to the Proxmox hypervisor and can use pct exec to run commands as root inside any LXC container. Write me a playbook that runs on pve and bootstraps the containers via pct exec.”
Claude: Generates the bootstrap-via-pve playbook. Works on first run.
This is the pattern. I provide direction and domain knowledge. Claude provides implementation speed and breadth of syntax knowledge. When something fails, we debug together — but the architectural insight (“use pct exec from the hypervisor”) came from 26 years of knowing how Proxmox works. Claude could not have figured that out from the error message alone.
Security Hardening
After building out the infrastructure, I asked Claude to do a security review:
Me: “Review the entire repo for security issues. Check for exposed secrets, weak permissions, missing access controls, and architectural vulnerabilities. Flag anything from critical to informational.”
Claude walked through every file — compose files, Ansible playbooks, Caddyfile, ansible.cfg — and produced a prioritized list of findings. Some were real (vault password not in .gitignore, Open WebUI binding to 0.0.0.0), some were acknowledged risks with justification (host_key_checking disabled, tls_insecure_skip_verify for self-signed backend certs). The review is documented in Post 6.
The key insight: AI is excellent at pattern-matching against known security anti-patterns. It will catch the things you have gone blind to. But it needs you to evaluate the findings — not every flagged item is actionable in every context.
Troubleshooting and Debugging
When an Ansible playbook fails, the error output is often verbose and cryptic. Pasting the full traceback into Claude Code and asking “what went wrong and how do I fix it?” consistently produces useful analysis. It parses the error, identifies the root cause, and suggests a fix — often with an explanation of why the original approach failed.
Example: the group_vars placement issue. Ansible was silently ignoring variables in ansible/group_vars/all.yml because the inventory was at ansible/inventory/hosts.yml. The group_vars directory needs to be adjacent to the inventory file, not at the project root. Claude identified this from the symptoms and suggested the fix. This is the kind of structural issue that would take an hour of documentation reading to diagnose on your own.
Documentation and Blog Writing
Every post in this series was drafted by Claude during or immediately after the technical work. The workflow:
- Do the technical work (write playbooks, debug issues, deploy services)
- Ask Claude to write a blog post about what we just did
- Review and refine — adjust tone, add personal anecdotes, cut fluff, verify accuracy
The first drafts are solid. They capture the technical details, structure the narrative, and include code examples. What they lack is personal voice and the “I have been doing this for 26 years” perspective that turns a tutorial into a story. That refinement is the human contribution, and it matters.
Best Practices for Working with AI Assistants
After building an entire homelab infrastructure with Claude Code, here is what I have learned about getting the most out of the collaboration.
Set Context Aggressively
The CLAUDE.md file in the repo root is Claude Code’s persistent memory. Mine describes the repo structure, the deployment workflow, the network layout, API credentials location, and conventions. Every conversation starts with Claude already knowing the environment. Invest time in this file — it pays for itself on every interaction.
Task Incrementally
Do not ask for everything at once. “Set up my entire homelab” will produce a wall of generic configs. Break it down:
- “Design the repo structure for Ansible and Docker Compose stacks”
- “Write the Ansible inventory for these hosts”
- “Write a bootstrap playbook that creates the admin user with SSH key auth”
- “Now add SSH hardening to the common playbook”
Each step builds on the last. Each step is small enough to review, test, and commit before moving on. This mirrors good engineering practice and it produces better AI output.
Review Everything
This should go without saying, but I will say it anyway: review every line of AI-generated output before deploying it. Not just a quick skim — actually read the Ansible playbook, trace the logic, check the file permissions, verify the module parameters. Claude is good, but it is not infallible. It will occasionally:
- Use deprecated module syntax
- Set permissions that are too permissive
- Miss edge cases in error handling
- Generate configs that work in general but not in your specific environment
Reviewing is not a waste of time. It is how you learn, how you catch bugs, and how you maintain ownership of your infrastructure.
Understand the Limitations
Claude Code does not have access to your running systems (unless you give it SSH access via MCP or similar). It cannot ping your hosts, check if a service is running, or verify that a config file was deployed correctly. It works from the information you provide and the code in your repo.
This means:
- When something fails, you need to provide the error output
- When you need to verify a deployment, you run the commands and report back
- When the environment has changed since the last conversation, you need to update the context
The AI is a pair programmer, not a replacement for running ansible-playbook --check and verifying the output.
Anti-Patterns: What Not to Do
Blind Copy-Paste
The most dangerous anti-pattern is copying AI-generated code into production without understanding it. If you cannot explain what every line does, you should not deploy it. This applies doubly to security-sensitive configurations like sudoers rules, firewall configs, and TLS settings.
Ignoring Security Implications
AI will generate functional code. That does not mean it generates secure code. Always ask yourself:
- What permissions does this file have?
- What network interfaces is this service binding to?
- Are there secrets in this config that should be in Vault?
- What happens if this service is compromised?
If Claude generates a Docker Compose file that binds to 0.0.0.0, ask whether it should bind to 127.0.0.1 instead. If it generates an Ansible playbook that sets file permissions to 0644, consider whether 0600 would be more appropriate. Security is your responsibility, not the AI’s.
Over-Relying Without Understanding
Using Claude Code to generate Ansible playbooks is productive. Using Claude Code to generate Ansible playbooks without learning how Ansible works is building on sand. The AI accelerates you, but you need a foundation of understanding to evaluate the output, debug failures, and make architectural decisions.
If you find yourself unable to modify the AI’s output without asking the AI for help, you have crossed the line from “leverage” to “dependency.” Step back, read the docs, build the mental model.
Not Committing Incrementally
When AI generates code quickly, the temptation is to make huge changes and commit once at the end. Do not do this. Commit after each logical change. Small, atomic commits with descriptive messages are your safety net. When something breaks (and it will), you need to know exactly which change caused it.
Tips for Getting the Most Out of Claude Code
CLAUDE.md Is Your Secret Weapon
The CLAUDE.md file at your repo root is loaded automatically at the start of every conversation. Use it to document:
- What the project is and how it is structured
- Key architecture decisions and why they were made
- Deployment workflows
- Where credentials live (not the credentials themselves)
- Conventions and coding standards
A good CLAUDE.md turns Claude from a general-purpose assistant into a domain-specific expert for your project.
Use Slash Commands
Claude Code has built-in slash commands that streamline common workflows:
/commit— stages changes and creates a commit with a descriptive message/review-pr— reviews a pull request for issues/help— shows available commands
These are faster than typing out “please create a git commit for the current changes.”
MCP Servers Extend Capabilities
Model Context Protocol (MCP) servers let you give Claude Code access to external tools and data sources. Examples:
- A GitHub MCP server for reading issues and PRs
- A filesystem MCP server for broader file access
- Custom MCP servers for your specific APIs (Portainer, Cloudflare, etc.)
MCP turns Claude Code from a code-generation tool into an integration platform. If you find yourself repeatedly copy-pasting output from one tool into Claude, that is a signal you should set up an MCP server.
Context Windows Are Finite
Claude Code maintains conversation context, but it has limits. For long sessions:
- Start new conversations for unrelated tasks
- Summarize decisions and state when context gets deep
- Use the CLAUDE.md file for persistent context rather than repeating it in every prompt
Verify Before You Trust
After Claude generates a configuration:
# Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('docker-compose.yml'))"
# Validate Ansible playbooks
ansible-playbook playbook.yml --syntax-check
# Dry-run before applying
ansible-playbook playbook.yml --check --diff
# Test the config before deploying
docker compose -f docker-compose.yml config
Validation is fast. Rolling back a broken deployment is not.
The Meta Moment
I want to acknowledge the elephant in the room: this blog post was written with the help of the tool it is about. Claude Code drafted it. I reviewed it, refined it, and approved it. That is not a contradiction — it is the whole point.
The value is not that AI wrote a blog post. The value is that the blog post exists at all. Without Claude Code, this would be on my “I should write about this someday” list. Instead, it is written, published, and part of a coherent series documenting the entire homelab journey.
That is the force multiplier. Not replacing the human. Making the human more prolific.
What is Next
The rest of this series dives into the technical details:
- Why IaC? — The motivation and repo design
- Ansible Bootstrap — Getting Ansible talking to every host
- GitOps with Portainer — Auto-deploying Docker stacks from Git
- Caddy DMZ Migration — Isolating the reverse proxy
- Security Review — Auditing everything we built
- IaC Lessons Learned — What worked, what did not
- Sudoers Demystified — Privilege escalation deep dive
- File Permissions — The bits that protect your homelab
- Automating Sudoers — Never manually edit sudoers again
- Anti-Patterns — Mistakes that break things at 3 AM
Every post was built with Claude Code. Every commit tells the story. Let us get into it.