Zum Inhalt springen
DE | EN
Zurück

Building My Home Infrastructure with Claude Code — An Honest Account

Building My Home Infrastructure with Claude Code — An Honest Account

In Article 1 I wrote that building my infrastructure taught me how to navigate the pitfalls of Claude Code safely. That wasn’t an empty promise. This article is where I deliver on it.

What I’ve built: a fully self-hosted blog stack — Ghost as a headless CMS, Astro as a static frontend generator, Gitea Actions as the CI/CD pipeline, Nginx as a reverse proxy, and Cloudflare as the only external dependency. Everything runs on two Raspberry Pis in the home network, secured with a firewall architecture that holds its own against most corporate environments.

Claude Code was part of the build. But not as an autopilot — as an assistant I watched over at every single step.

One thing upfront: this infrastructure isn’t a finished product — it’s a living structure. Simultaneously my production setup and my playground for experimentation. What looks like this today may look different tomorrow. That’s not a bug, that’s the concept.


The Project at a Glance

Home lab infrastructure doesn’t happen overnight. It grows in phases — each building on the previous one, each with its own lessons. Here’s where things stand:

flowchart LR
    P1[✅ Phase 1
Gitea · raspi-5-1] --> P2[✅ Phase 2
Web Stack · raspi-4-1]
    P2 --> P3[✅ Phase 3
Astro · Design]
    P3 -.-> P4

    P4[✅ Phase 4
CI/CD Pipeline] --> P5[✅ Phase 5
Polish · NAS]
    P5 --> P6[⏳ Phase 6
Authentik · SSO]
    P6 -.-> P7

    P7[⏳ Phase 7
Agentic Core]

    class P1 success
    class P2 success
    class P3 success
    class P4 success
    class P5 success
    class P6 warning
    class P7 warning

This article covers Phase 1 through 4 — from the Gitea installation to a running CI/CD pipeline. Phase 5 (backup scripts, NAS integration, monitoring) is also complete, but is the subject of its own article. What’s planned for Phase 6 and 7 is at the end as a sneak preview.


Phase 1 & 2: Foundation — Gitea and Web Stack

Before we get to Claude Code, a quick look at the infrastructure itself. Because that’s the context that shapes everything else.

graph TD
    Internet([🌐 Internet]) --> CF[Cloudflare\nZero Trust Tunnel]
    CF --> UCG[UniFi UCG Max\nFirewall / Router]
    UCG --> Pi4

    subgraph Heimnetz["🏠 Home Network"]
        subgraph DMZ["DMZ Zone"]
            Pi4[Raspberry Pi 4-1\nNginx · Astro\nOpenWebUI]
        end
        subgraph LAN["LAN Zone"]
            Pi5[Raspberry Pi 5\nGitea · NAS]
            Pi4b[Raspberry Pi 4-2\nOllama]
        end
        Pi4 <--> Pi5
        Pi5 <--> Pi4b
    end

    UCG --> Pi5
    Dev([💻 MacBook\nDevelopment]) --> Pi5
    Dev --> Pi4

    class CF cloud

→ arrow = connection / data flow → double arrow = bidirectional internal connection (LAN) → Cloudflare: only external dependency — no open port

What’s running here:

Why Cloudflare Is the Only External Dependency

No open port in the router. No DynDNS. No self-managed TLS certificate that expires and causes a 3am outage. The Cloudflare Tunnel establishes an outbound connection from my Pi to Cloudflare’s infrastructure — no port is exposed externally, there’s no direct attack surface.

That’s a deliberate security decision, not a convenience. Running a home server with an open port 443 exposes you to a very different risk profile than someone who applies Zero Trust as an architectural principle.

Security That Holds Its Own Against Corporate Environments

I occasionally hear the objection: “A home lab isn’t a serious setup.” I disagree.

What’s implemented here:

With two users, there’s really only one word to say about performance: enough.


Phase 3 & 4: Stack and Pipeline

Git is the single source of truth. Articles are written as Markdown files in the codecrafter-articles repo — no CMS, no webhook, no external data source. What’s in the repo appears on the blog. What isn’t, doesn’t.

The decision came after a brief experiment with Ghost: two sources of truth don’t work. Changes in Ghost would have to be synced back to Git, because Git is the single source of truth. So Ghost is out. Everything out that doesn’t pull its weight.

flowchart LR
    A[📝 Article
Markdown in Git] --> B[Gitea
dev branch]
    B --> C[Dev Stage
Astro Build]
    C --> D[👤 Review
Dev Stage]
    D -.-> E

    E[Pull Request
to main] --> F[👤 Review
Code Review]
    F --> G[Gitea Actions
Astro Build]
    G --> H[👤 Approval
Manual Gate]
    H -.-> I

    I[Nginx Prod
Docker] --> J[🌐 Reader]

    class F cloud
    class H cloud
    class J success

Git branching as a publishing workflow: The dev branch is the drafting area — articles are written here, and the dev stage displays them for review. Only a pull request to main and a successful merge trigger the production build.

Astro with the AstroPaper theme reads directly via Content Collections from the repo — static HTML pages, no server-side rendering, no database queries on page load. Fast, secure, low maintenance.

Gitea Actions handles the CI/CD pipeline: every merge to main triggers an Astro build. Two quality gates stand between commit and going live — nothing is published without explicit approval.


Claude Code in Action: What I Actually Did

Now for the actual subject. How did I use Claude Code during the build — and what did I learn from it?

The Ground Rule: Only What I Can Evaluate

I set myself a clear rule: Claude Code only gets involved where I can judge the result myself. That sounds obvious — but in practice it isn’t always. The temptation is real to just take a generated YAML block because it “looks plausible.”

I deliberately didn’t do that. Every step was approved only after I understood what it does.

What Claude Code Did Well

Gitea Actions workflows were a clear strength. YAML syntax is verbose and error-prone to write — a single wrong space is enough to break everything. In operation it’s very stable and reliable. Claude Code delivered working scaffolding that I then adapted and reviewed.

# Example: Generated workflow scaffold for Astro build
name: Build and Deploy
on:
  repository_dispatch:
    types: [ghost-webhook]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm ci
      - name: Build Astro site
        run: npm run build
        env:
          GHOST_API_URL: ${{ secrets.GHOST_API_URL }}
          GHOST_CONTENT_API_KEY: ${{ secrets.GHOST_CONTENT_API_KEY }}

Nginx configurations for reverse proxy and SSL termination — Claude Code provided solid starting points. The Cloudflare-specific headers I had to add myself — exactly the scenario I described in Article 1: Claude Code doesn’t know my context unless I provide it.

Bash scripts for deployment, backup, and monitoring — this is where Claude Code was most reliable. Contained tasks, well-defined inputs and outputs, easy to review.

Where I Had to Correct Things

Security-relevant configurations — this is where I was most critical. Claude Code generated Nginx configurations that were technically correct but not security-hardened. Missing security headers, overly permissive CORS settings, default values that have no place in production.

That’s not a criticism of Claude Code. It’s confirmation of the core argument from Article 1: the model doesn’t know my security requirements unless I explicitly put them in the prompt.

Gitea-specific quirks — Gitea Actions is very similar to GitHub Actions, but not identical. Claude Code generated GitHub Actions syntax multiple times that simply doesn’t work in Gitea. After the first time, I always mentioned it explicitly in the prompt: “This runs on Gitea Actions, not GitHub Actions.”

Infrastructure as Code structure — Claude Code produced working but unmaintainable scripts. Too many hardcoded values, no parameterisation, no error handling. I always cleaned that up — and learned a lot in the process.

What I Took Away From It

This is the actually valuable part. Not the generated code — but what I learned through critically reviewing it.

When I review an Nginx block that Claude Code generated, I have to understand why every directive is there. That forces me to go deeper into the documentation than I might have without the AI starting point. Claude Code didn’t take the work away from me — it gave me something to think about.

That’s the usage pattern I’d recommend: Claude Code as a conversation partner and starting point, not as an autopilot.


The Result: A Pipeline That Works

After several weeks of building, review cycles, and adjustments, the stack is running stably. Here’s the full deployment flow with both quality gates:

sequenceDiagram
    participant T as Thomas
    participant DEV as dev branch
    participant DS as Dev Stage
    participant PR as Pull Request
    participant GA as Gitea Actions
    participant AP as Manual Approval
    participant PROD as Nginx Prod
    participant CF as Cloudflare

    T->>DEV: Commit article
    DEV->>DS: Dev Stage build
    T->>DS: ✅ Visual review
    T->>PR: Pull request to main
    T->>PR: ✅ Code review
    PR->>GA: Merge → trigger build
    GA->>AP: Astro build complete
    T->>AP: ✅ Manual approval
    AP->>PROD: Deploy to production
    PROD->>CF: Cache invalidation via API
    Note over CF: Readers see new version

Two quality gates — visual review on the dev stage and manual approval before the production deploy — before anything goes live. Every step is deliberate; no automation replaces the human review.



Phase 6 & 7: What’s Coming Next

This is Phase 4 — and it’s running. What will be covered in Part 2 of this article is still being built. But it’s worth a look ahead.

Authentik — Single Sign-On for All Services

A self-hosted identity provider based on Authentik: OAuth2/OIDC, MFA with TOTP and WebAuthn. One login for Gitea, Nginx Proxy Manager, OpenWebUI, and the Agentic Automation Core. Once you’ve run SSO in a homelab, you don’t want to go back.

Agentic Automation Core (Phase 7)

A LangGraph-based agent for invoice processing — fully self-hosted, with human-in-the-loop as a core principle. LiteLLM as an inference gateway routes to cloud or local depending on the privacy requirements. RAG over past invoices, a Pydantic schema as a quality gate, NTFY push notification when a human decision is needed.

The article covering this already exists: Processing Invoices with AI — the architecture is described in full there. Once Phase 7 is live, this article will be updated with the real-world experience.

Mac Mini as Inference Server

Probably. Apple Silicon with unified memory is in a different league from a Raspberry Pi for local LLM inference. Integrated seamlessly via LiteLLM — no agent code changes needed, just a new route in the gateway configuration.

Planned, not yet built. But the spot on the shelf is already reserved. 😄

Conclusion: Build It Yourself, Understand It Yourself, Own It Yourself

What I took away from this project goes beyond the tech stack.

First: a home lab can — with the right architectural approach — be a serious infrastructure. Zone architecture, Zero Trust, Infrastructure as Code, automated pipelines with manual gates — none of that is an enterprise exclusive.

Second: Claude Code is an excellent tool for exactly this kind of context — when used correctly. As a starting point, as a conversation partner, as an accelerator for tasks you can evaluate yourself. Not as a substitute for your own understanding.

Third: the review process isn’t the overhead — it’s the value. I learned more about Nginx, Gitea Actions, and Bash by critically working through Claude Code output than I would have from passively reading documentation.

Self-hosting isn’t nostalgia or protest for me. It’s a deliberate choice for control, understanding, and sovereignty — over my data, my infrastructure, and my tech stack.


This article reflects my personal views exclusively and has no connection to any professional affiliation.


Artikel teilen:

Vorheriger Artikel
Meine Heiminfrastruktur mit Claude Code aufgebaut — ein ehrlicher Erfahrungsbericht
Nächster Artikel
Masterbuilt Gravity 1050: Bypass Panel — vom Design zum fertigen Teil