Introduction
I have always enjoyed working with infrastructure. At work, that often means thinking about deployment pipelines, cloud resources, and how applications fit together. I wanted to explore some of the same ideas at home, and to have more control over the software and devices I use every day.
That is how my homelab started: a small HP EliteDesk, Debian, Docker, and a growing list of things I wanted to automate.
The goal was not to run as many services as possible. I wanted a setup that was useful, maintainable, and possible to rebuild when something went wrong. That led to a few practical choices about the host, deployment, and how I access the services.
Starting with a small machine
I chose an HP EliteDesk as the host. It is compact, relatively inexpensive, and capable of running the services I had in mind. I installed a minimal version of Debian 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.
This is not a Kubernetes cluster. Kubernetes is interesting, but I do not need an orchestrator to run a handful of services on one machine. Docker Compose solves this problem with less complexity.
Treating my homelab like a software project
I chose 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 does not make the system completely hands-off. Some applications still require one-time setup or account authentication. It does give me a reproducible foundation, so I can understand and repeat the steps used to configure the machine. When I find a problem, I can fix it in Ansible instead of making a one-off change on the server, and the next deployment benefits from that correction.
The services I actually use
The setup has grown beyond my original plan. These are the services I currently use and what each one does:
| 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 experiment with, but Home Assistant is where the homelab 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 do not want a smart home that requires me to open an app for everything. Ideally, the house responds to what I am 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.
This is a small-scale version of a familiar engineering problem: translating imperfect events into useful decisions. The automation is only as reliable as the assumptions it makes about those signals.
What I want to build next
There are still several things I would like to improve: presence detection, sleep and morning routines, and 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 individual tools are familiar; the more interesting part has been deciding how they fit together and how to keep the setup understandable as it grows.
For me, the practical test is whether it makes day-to-day life easier. The homelab is useful when the services work reliably and the system remains manageable; adding another container is not a goal by itself.