Introduction
I’ve always enjoyed working with infrastructure. At work, that often means thinking about deployment pipelines, cloud resources, and how applications fit together. Recently, I wanted to explore some of those same ideas at home. Not because my apartment needed its own miniature data center, but because I liked the idea of having more control over the software and devices I use every day.
That’s how my homelab started: a small HP EliteDesk, Debian, Docker, and an increasingly long list of things I wanted to automate.
The goal wasn’t simply to run as many services as possible. I wanted a setup that was useful, maintainable, and easy to rebuild when something inevitably went wrong.
Starting with a small machine
I chose an HP EliteDesk as the host. It’s compact, relatively inexpensive, and more than capable of running the services I had in mind. Rather than installing a desktop environment, I went with a minimal Debian installation and manage the machine remotely over SSH.
Most applications run in Docker containers. This keeps their dependencies isolated and makes it easier to upgrade, replace, or troubleshoot individual services. The host itself stays relatively simple.
The distinction between the host and the containers is deliberate:
- Debian provides the operating system and the services that need to interact closely with it.
- Docker Compose describes the application stack.
- Ansible provisions the machine and deploys the configuration.
- Tailscale provides private remote access when I’m away from home.
It’s not Kubernetes, and that’s intentional. Kubernetes is interesting, but I don’t need an orchestrator to run a handful of services on a single machine. Docker Compose solves the problem with considerably less complexity.
Treating my homelab like a software project
One of the first decisions I made was to manage the setup with Ansible instead of configuring everything manually.
I could SSH into the machine, install Docker, create directories, and start containers by hand. That would work. The problem is that, after a few months, I’d probably forget exactly what I changed and why.
With Ansible, the configuration lives in Git. The playbook describes the desired state of the machine: which packages should be installed, which directories should exist, how services are configured, and which containers should be running.
The repository is roughly organized like this:
-
elitedesk-homelab/
-
inventory/
-
group_vars/
-
all/
-
-
roles/
-
base/
-
docker/
-
storage/ Container directories
-
tailscale/
-
samba/
-
homelab/ Compose configuration and deployment
-
- site.yml
-
For example, the storage role creates the directories used by the containers, while the homelab role renders the Docker Compose configuration and starts the services.
Secrets are handled separately using Ansible Vault. Passwords and API keys shouldn’t end up in a public repository, but I still want to be able to deploy the stack without manually entering every credential into every application.
This approach doesn’t make the system completely hands-off. Some applications still require a one-time setup or account authentication. But it gives me a reproducible foundation, which is much more valuable than a collection of undocumented shell commands.
The services I actually use
My setup has grown beyond the original plan. It now covers a few different areas.
| Area | Services | Purpose |
|---|---|---|
| Smart home | Home Assistant | Connect devices and coordinate automations |
| Media | Jellyfin | Stream my own media library |
| Media management | Seerr, Radarr, Sonarr, Prowlarr, qBittorrent | Organize media requests and downloads |
| Files | Nextcloud, Samba | Personal file storage and network shares |
| Networking | Pi-hole, Caddy, Tailscale | DNS, friendly local URLs, and remote access |
| Management | Portainer, Homepage | Container management and a service dashboard |
Making the services easy to reach
Remembering port numbers gets old quickly. Instead of opening 192.168.x.x:7878 for one application and another port for the next, I use Caddy as a reverse proxy and Pi-hole for local DNS.
That gives me addresses such as:
ha.home.arpajellyfin.home.arpanextcloud.home.arpahomepage.home.arpaThe .home.arpa domain is intended for home networks. Requests resolve to my homelab, and Caddy forwards them to the correct service.
Tailscale provides access to the machine outside my home network without exposing the application ports directly to the public internet. Getting private DNS to work consistently across devices is another piece of the puzzle, but the overall approach is straightforward: keep the services private and make access convenient.
Home Assistant is where things get interesting
The infrastructure is fun to build, but Home Assistant is where it starts to affect everyday life.
I live in a two-floor apartment. Downstairs I have the living room, kitchen, and balcony. Upstairs are the bedroom, bathroom, and my home office. That layout gives me quite a few opportunities for automations, especially around presence and lighting.
I don’t want a smart home that requires opening an app for everything. Ideally, the house should respond to what I’m doing without becoming unpredictable.
For example, I’m interested in distinguishing between being home and being in a particular room. Those are different signals. Knowing that I’m home is useful for general automations, but knowing that I’ve moved from the living room to my office could help determine which lights should be active.
That also introduces an interesting software-design problem: sensor readings aren’t the same as state. A motion sensor might stop detecting movement while I’m sitting still. A phone might be charging while I’m still awake. Reliable automation needs to combine signals, account for uncertainty, and avoid switching states too aggressively.
It’s a small-scale version of a familiar engineering problem: translating imperfect events into useful decisions.
The less glamorous part: debugging
Self-hosting is often presented as a dashboard full of green status indicators. My experience has involved considerably more permission errors.
A few examples:
- Nextcloud couldn’t write to its data directory because the container’s user didn’t match the host directory ownership.
- Homepage failed while creating its logs directory because I’d mounted its configuration as read-only.
- qBittorrent credential provisioning failed because a Python regular-expression replacement interpreted a backslash in a configuration key as an escape sequence.
None of these issues is particularly exciting on its own. Together, they illustrate why deployment configuration deserves the same care as application code.
The advantage of managing the setup in Ansible is that I can fix these problems in the repository rather than applying one-off changes to the server. The next deployment then benefits from the correction.
What I want to build next
There are several directions I’d like to explore: better presence detection, more reliable sleep and morning routines, and improved backup and recovery procedures.
I’d also like to make the setup easier to observe. It’s one thing to know whether a container is running; it’s another to understand whether the service is healthy, how much storage it’s using, and whether an automation is behaving as expected.
Most importantly, I want to keep the system maintainable. It’s tempting to add another service every time I discover an interesting open-source project. But every additional container introduces configuration, storage, updates, and potential failure modes.
The best homelab isn’t necessarily the one with the most services. It’s the one that continues to be useful after the initial excitement of setting it up.
Final thoughts
What started as a small server project has become a way to experiment with infrastructure, automation, and software architecture outside work.
The technologies aren’t especially exotic. Debian, Docker, Ansible, and Home Assistant are all established tools. The interesting part is deciding how they should fit together, which responsibilities belong where, and how to keep the whole setup understandable as it grows.
And unlike a typical side project, this one has a very practical test: does it make my day-to-day life easier?
That’s the part I’m most interested in finding out.