The Tools Behind My Homelab and How They Fit Together
People ask what is actually running in my basement. The honest answer is a lot of small, open source tools, and the interesting part is not any single one of them but how they are stitched together: a merge request becomes a deployment, a backup leaves the house once, one login opens everything. This post is the map, a few stories along its lines, and a showcase of every tool on it.
The Tools at a Glance
Each tile is a tool that really runs here, and each line says what one hands to the next. The highlighted paths are the five stories below. Hover or focus a story to light up its path, and the dot on a tile means that tool signs in with Authentik.
Simplified on small screens - tap Enlarge for the full map.
- One git push to productionGitLab, Renovate and Flux turn a merge into a deployment, and GitLab CI runners apply OpenTofu and run Ansible for the Docker stacks.
- Backups leave the house oncerestic and GitLab write to Garage at home, one rclone sync sends the copy to Backblaze B2.
- One login for everythingAuthentik sits behind the tunnel and in front of the apps.
- Secrets never in gitVault feeds the cluster and the deploy runs.
- One door for my agentsClaude Code and OpenCode reach photos, documents, metrics and logs through one MCP gateway.
If you want the history of how the lab got here, start with Reflecting on Progress: My Homelab Journey Since 2020 and Homelab: The Next Iteration. This post is the current inventory.
One Git Push to Production: GitLab, Renovate and Flux
Everything starts in GitLab: git, CI and the container registry on one server, and the repositories for the Ansible configuration and for the cluster. Renovate scans the repositories and opens a merge request for each new image, chart or provider version. When I merge, Flux, which pulls the cluster repository from GitLab, reconciles the Kubernetes cluster to what Git now says; a CI job in that repository only commits the new image tag, and Flux does the rest. The images come from the same GitLab registry. Flux then posts the result to Mattermost, so I find out about a red deployment from chat, not from a user. The cluster itself runs on Talos Linux on Proxmox VMs, which OpenTofu defines, with the code and the state in GitLab and its CI runners planning and applying it. On the Docker side the same runners run the Ansible playbooks, which render every Compose stack on the hosts.
The payoff is not speed. It is that I can recreate a service from what is in Git. A machine or an app that only exists because I once clicked something is a liability.
Backups Leave the House Once: restic, Garage and Backblaze B2
Each Docker stack carries a stack-back sidecar that runs restic and writes an encrypted repository to Garage, a small S3-compatible object store in my basement. GitLab’s own backups land in Garage too. The first copy never leaves the house. Only a sync job leaves it, once: it mirrors the backup buckets with rclone to Backblaze B2, so there is a second copy in another building. Prometheus scrapes Garage, so a full disk shows up as an alert before it shows up as a failed backup. The longer story is in the backup architecture: configuration is backed up per stack, and media is deliberately excluded.
One Login for Everything: Authentik and Cloudflare Tunnel
My connection is DS-Lite, so there is no public IPv4 to forward a port to. That turned out to be a gift: nothing at home listens on the internet. A Cloudflare Tunnel connects outbound to Traefik, Cloudflare terminates the public hostname, and Access policies decide who gets through. Behind Traefik, Authentik is the one login. GitLab, Vault, Grafana, Immich, Paperless and Linkwarden speak OIDC to it, and where an app does not, a forward-auth middleware puts the login in front of it. I wrote up two examples: Vault SSO with Authentik and Immich behind Cloudflare Tunnel and Access. The background for the tunnel is in Cloudflare: DNS Migration and Tunnel Integration.
Secrets Never in Git: Vault and External Secrets
HashiCorp Vault is where credentials live. Ansible looks them up while it renders a stack, and in the cluster External Secrets turns Vault entries into Kubernetes Secrets, so a pod gets its password without the password ever being committed. Both repositories can therefore be read by anyone without handing out the keys.
Logs, Metrics and Alerts: Alloy, Loki, Prometheus and Grafana
Traefik sends its access logs to Alloy, and Alloy agents on every host ship container logs to Loki and metrics to Prometheus. Grafana reads Loki, Prometheus and Tempo, and its alerts, like those from Uptime Kuma, end up in Mattermost. I trust the graphs more than my memory: several of the stories in this blog were found by reading logs, not by guessing.
One MCP Gateway for My Agents
The newest layer sits on top of all this and is for my AI agents: a small gateway of my own, deployed by Flux like everything else, that gives Claude Code and OpenCode one endpoint and one login, with Authentik in front, for the MCP servers wrapping Immich, Paperless, Linkwarden, Jellyfin, the observability stack, Vast.ai and Google Workspace. In the map it is the agent layer at the top.
How it works, which servers sit behind it and why I built it myself is told on How I work, because the gateway is the culmination of how I work with agents.
Tool Showcase
Most of these are open source, the rest are free to run yourself. Every card links to the project itself.
Agents
MCP gateway
Self-built. One endpoint and one Authentik login for the tool servers I wrote: photos, documents, bookmarks, metrics and logs.
Claude Code
The coding agent I work with. It signs in to the gateway once and gets every tool.
OpenCode
An open source coding agent for the terminal, connected to the same gateway with its own login.
Platform
Proxmox VE
The hypervisors. Every VM, including the Talos nodes, lives here.
Talos Linux
The operating system of the cluster nodes: immutable, configured through an API, no SSH, no drift.
Kubernetes
Runs what I want to ship like software: agent workers, MCP servers, small jobs and this blog.
Docker
Compose stacks on a handful of VMs for everything stateful, rendered by Ansible from a GitLab CI pipeline.
Ansible
Builds the hypervisors and renders every Compose stack, run by GitLab CI from the infra repository, with secrets looked up at deploy time.
OpenTofu
Defines the Proxmox VMs and the Talos cluster, plus the Cloudflare, Authentik, Garage and Grafana configuration. The code and its state live in GitLab, and its CI runners plan and apply it.
Delivery
GitLab
Self-hosted git, CI and the container registry. It holds the OpenTofu, Ansible and cluster repositories, and its CI runners apply and run them. Almost everything starts with a merge request here.
Renovate
Opens a merge request for every new version of an image, chart or provider.
Flux
Pulls the cluster repository from GitLab and reconciles the cluster to it, so a merge is the deployment. It also reports the result to chat.
Cloudflare
DNS, Tunnels and Access: the only way in from outside, with no open ports at home.
Traefik
The reverse proxy on the Docker hosts. Forward auth puts the login in front of apps that cannot do OIDC.
Identity and secrets
Authentik
One login for GitLab, Vault, Grafana, Immich, Paperless and Linkwarden, via OIDC or forward auth.
HashiCorp Vault
Where credentials live. Ansible and the cluster fetch them at deploy time instead of finding them in Git.
External Secrets
Turns Vault entries into Kubernetes Secrets, so pods get what they need and Git stays clean.
Storage and backup
Garage
A small S3-compatible object store. Backup repositories and GitLab backups land here first, at home.
Backblaze B2
The one copy that leaves the house. A sync job mirrors the backup buckets from Garage to B2.
restic
Encrypted, deduplicated backups, wired into every Compose stack through stack-back.
Observability
Grafana Alloy
The agent on every host. It ships container logs, Traefik access logs and metrics.
Loki
Stores the logs. Several incidents on this blog were found by reading them.
Prometheus
Metrics for hosts, services and Garage, and the source of most alert rules.
Tempo
Traces, so a slow request can be followed across services.
Grafana
Dashboards and alerting on top of Prometheus, Loki and Tempo, configured as code.
Uptime Kuma
Checks that services answer. Monitors are created from container labels.
Mattermost
Where alerts, deploy notices and pipeline results arrive, in separate channels.
Apps
Immich
The family photo library, signed in through Authentik.
Paperless-ngx
Scanned documents, OCR and tags in one searchable archive.
Linkwarden
Bookmarks with archived copies, so links survive link rot.
Solidtime
My own hour tracking: time on side projects, and how many hours my mobile-work days at Porsche really add up to.
Home Assistant
Home automation and the energy dashboard for the household, on a VM of its own.
Music Assistant
Plays my music on the speakers around the house, as a Home Assistant add-on.
Jellyfin
Plays my music and films.
AudioMuse-AI
Listens to the music with audio embeddings and builds playlists of tracks that sound alike.
RomM
The ROM library for the retro consoles, with metadata and artwork, signed in through Authentik.
UniFi Network
The controller for the network gear at home.
LiteLLM
One OpenAI-compatible endpoint in front of Mistral and the GPU endpoints I rent by the hour.
Lessons Learned
- Never rely on ip:port. Reaching a service by address and port works until the first phone, laptop or browser complains about a certificate. Everywhere in the lab I use a regular domain whose names point to private IPs, and Traefik gets trusted Let’s Encrypt certificates through the Cloudflare DNS challenge with an API key. Nothing has to be reachable from the internet for that, and client devices stop being a hassle. The details are in Cloudflare: DNS Migration and Tunnel Integration.
- Start with git, always. The first iterations of the lab ended in config rot: things were added by hand on the hypervisor, and I was playing system administrator more than providing services. It taught me a lot, but it cost flexibility and speed. Infrastructure as Code: From Manual Provisioning to Ansible + Terraform tells how I got out of it.
- Converge on similar approaches where possible. My Ansible stack had 10 to 15 roles, and each one had its own way of deploying and setting up a Docker Compose stack, besides actually running it. During a rebuild I converged them into what I call Platform One: three VMs that are configured in exactly the same way and differ only in the apps that run on them. Less variety means less to remember, and the same change works everywhere. Homelab: The Next Iteration covers where that went next.
Conclusion
The lab is not big because I like hardware, it is big because I like having my own versions of things. The pieces that made it livable were boring ones: Git as the source of truth, a login in front of everything, no open ports, backups that leave the house and logs I can read.
Logos are trademarks of their respective owners and are used here only to identify the tools they belong to. The logo of the self-built MCP gateway is the mark of the Model Context Protocol, not a product of mine. Most icon data comes from Simple Icons (CC0). The logos of Garage, restic, External Secrets, Grafana Alloy, Loki, Tempo, Linkwarden and OpenCode come from the projects themselves.