# The Tools Behind My Homelab and How They Fit Together

- Canonical: https://matheja.me/2026/10/09/what-i-host-in-my-homelab.html
- Date: 2026-10-09
- Author: Ben Matheja
- Tags: homelab, selfhosting, gitlab, flux, garage, s3, backblaze, vault, authentik, cloudflare, proxmox, talos, ansible, observability, mcp
- Site: Ben Matheja (https://matheja.me), llms.txt: https://matheja.me/llms.txt

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.

> **Diagram:** How my homelab tools are stitched together. Forty-five tools in a map, with labelled connections. Delivery: Renovate opens merge requests in GitLab, which holds the Ansible repository, the cluster repository and the container registry. GitLab is the home of the infrastructure as code: the OpenTofu and Ansible repositories live there, and its CI runners apply the OpenTofu code and run the Ansible playbooks that render the Docker Compose stacks. OpenTofu also manages Cloudflare DNS, the Tunnel and Access apps, and configures Authentik, Grafana and Garage (marked with a square); for the cluster they commit new image tags to the cluster repository, which Flux pulls from GitLab and reconciles into the Kubernetes cluster. Talos Linux is the immutable, API-only operating system of its nodes; OpenTofu defines the Proxmox VMs and the cluster; Ansible renders the Docker Compose stacks. Secrets: Vault feeds External Secrets, which creates the pod secrets in Kubernetes, and Vault is also read by Ansible at deploy time. Identity: Cloudflare Tunnel reaches Traefik, Authentik adds forward auth to Traefik, and GitLab, Vault, Grafana, Immich, Paperless and Linkwarden sign in with Authentik via OIDC. Backups: the apps' stack-back sidecars write restic repositories to Garage at home and GitLab backups go to Garage too; only one rclone sync leaves the house, copying the Garage buckets offsite to Backblaze B2; Observability: Traefik access logs go to Alloy, which ships logs to Loki and metrics to Prometheus; Grafana reads Loki, Prometheus and Tempo. Notifications: Flux, GitLab CI, Uptime Kuma and Grafana alerts all post to Mattermost. Apps: Immich, Paperless, Linkwarden, Solidtime, Home Assistant, Music Assistant, Jellyfin, AudioMuse, RomM, UniFi Network and LiteLLM run in the lab, most of them as Docker stacks. Agents: Claude Code and OpenCode connect to the self-built MCP gateway, which signs in with Authentik and routes to seven MCP servers: Immich, Paperless, Linkwarden, Jellyfin, Observability (Prometheus, Loki and Tempo), Google Workspace (Calendar and Contacts) and Vast (GPU models). Six are my own code, Google Workspace is an upstream project. The gateway reaches the Docker apps and the observability stack.

*The tools of my homelab as one map. Lines are labelled with what flows; highlighted lines belong to the five stories.*


If you want the history of how the lab got here, start with [Reflecting on Progress: My Homelab Journey Since 2020](https://matheja.me/2025/01/02/homelab-update.html) and [Homelab: The Next Iteration](https://matheja.me/2025/12/19/homelab-next-iteration.html). This post is the current inventory.

## One Git Push to Production: GitLab, Renovate and Flux

Everything starts in [GitLab](https://about.gitlab.com/): 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](https://github.com/lawndoc/stack-back) sidecar that runs restic and writes an encrypted repository to [Garage](https://garagehq.deuxfleurs.fr/), 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](https://matheja.me/2025/08/15/modular-backup-architecture.html): 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](https://goauthentik.io) 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](https://matheja.me/2025/11/20/vault-oidc-authentik-sso.html) and [Immich behind Cloudflare Tunnel and Access](https://matheja.me/2026/10/06/immich-photo-management-cloudflare-tunnel.html). The background for the tunnel is in [Cloudflare: DNS Migration and Tunnel Integration](https://matheja.me/2025/01/13/dns-migration-cloudflare.html).

## 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](https://code.claude.com/) and [OpenCode](https://opencode.ai/) 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](https://matheja.me/how-i-work/#mcp), 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](https://code.claude.com/): The coding agent I work with. It signs in to the gateway once and gets every tool.
- [OpenCode](https://opencode.ai/): An open source coding agent for the terminal, connected to the same gateway with its own login.

### Platform

- [Proxmox VE](https://www.proxmox.com/en/products/proxmox-virtual-environment/overview): The hypervisors. Every VM, including the Talos nodes, lives here.
- [Talos Linux](https://www.talos.dev/): The operating system of the cluster nodes: immutable, configured through an API, no SSH, no drift.
- [Kubernetes](https://kubernetes.io/): Runs what I want to ship like software: agent workers, MCP servers, small jobs and this blog.
- [Docker](https://www.docker.com/): Compose stacks on a handful of VMs for everything stateful, rendered by Ansible from a GitLab CI pipeline.
- [Ansible](https://www.ansible.com/): Builds the hypervisors and renders every Compose stack, run by GitLab CI from the infra repository, with secrets looked up at deploy time.
- [OpenTofu](https://opentofu.org/): 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](https://about.gitlab.com/): 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](https://docs.renovatebot.com/): Opens a merge request for every new version of an image, chart or provider.
- [Flux](https://fluxcd.io/): 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](https://www.cloudflare.com/): DNS, Tunnels and Access: the only way in from outside, with no open ports at home.
- [Traefik](https://traefik.io/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](https://goauthentik.io/): One login for GitLab, Vault, Grafana, Immich, Paperless and Linkwarden, via OIDC or forward auth.
- [HashiCorp Vault](https://www.vaultproject.io/): Where credentials live. Ansible and the cluster fetch them at deploy time instead of finding them in Git.
- [External Secrets](https://external-secrets.io/): Turns Vault entries into Kubernetes Secrets, so pods get what they need and Git stays clean.

### Storage and backup

- [Garage](https://garagehq.deuxfleurs.fr/): A small S3-compatible object store. Backup repositories and GitLab backups land here first, at home.
- [Backblaze B2](https://www.backblaze.com/cloud-storage): The one copy that leaves the house. A sync job mirrors the backup buckets from Garage to B2.
- [restic](https://restic.net/): Encrypted, deduplicated backups, wired into every Compose stack through stack-back.

### Observability

- [Grafana Alloy](https://grafana.com/docs/alloy/latest/): The agent on every host. It ships container logs, Traefik access logs and metrics.
- [Loki](https://grafana.com/oss/loki/): Stores the logs. Several incidents on this blog were found by reading them.
- [Prometheus](https://prometheus.io/): Metrics for hosts, services and Garage, and the source of most alert rules.
- [Tempo](https://grafana.com/oss/tempo/): Traces, so a slow request can be followed across services.
- [Grafana](https://grafana.com/oss/grafana/): Dashboards and alerting on top of Prometheus, Loki and Tempo, configured as code.
- [Uptime Kuma](https://uptime.kuma.pet/): Checks that services answer. Monitors are created from container labels.
- [Mattermost](https://mattermost.com/): Where alerts, deploy notices and pipeline results arrive, in separate channels.

### Apps

- [Immich](https://immich.app/): The family photo library, signed in through Authentik.
- [Paperless-ngx](https://docs.paperless-ngx.com/): Scanned documents, OCR and tags in one searchable archive.
- [Linkwarden](https://linkwarden.app/): Bookmarks with archived copies, so links survive link rot.
- [Solidtime](https://www.solidtime.io): My own hour tracking: time on side projects, and how many hours my mobile-work days at Porsche really add up to.
- [Home Assistant](https://www.home-assistant.io/): Home automation and the energy dashboard for the household, on a VM of its own.
- [Music Assistant](https://music-assistant.io/): Plays my music on the speakers around the house, as a Home Assistant add-on.
- [Jellyfin](https://jellyfin.org/): Plays my music and films.
- [AudioMuse-AI](https://github.com/NeptuneHub/AudioMuse-AI): Listens to the music with audio embeddings and builds playlists of tracks that sound alike.
- [RomM](https://romm.app/): The ROM library for the retro consoles, with metadata and artwork, signed in through Authentik.
- [UniFi Network](https://ui.com/): The controller for the network gear at home.
- [LiteLLM](https://www.litellm.ai/): 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](https://matheja.me/2025/01/13/dns-migration-cloudflare.html).
- **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](https://matheja.me/2024/04/15/infrastructure-as-code-journey.html) 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](https://matheja.me/2025/12/19/homelab-next-iteration.html) 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.

<p class="icon-credit">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 <a href="https://simpleicons.org" rel="noopener">Simple Icons</a> (CC0). The logos of <a href="https://garagehq.deuxfleurs.fr/" rel="noopener">Garage</a>, <a href="https://restic.net/" rel="noopener">restic</a>, <a href="https://external-secrets.io/" rel="noopener">External Secrets</a>, <a href="https://grafana.com/docs/alloy/latest/" rel="noopener">Grafana Alloy</a>, <a href="https://grafana.com/oss/loki/" rel="noopener">Loki</a>, <a href="https://grafana.com/oss/tempo/" rel="noopener">Tempo</a>, <a href="https://linkwarden.app/" rel="noopener">Linkwarden</a> and <a href="https://opencode.ai/" rel="noopener">OpenCode</a> come from the projects themselves.</p>
