1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

Part 1 of 6

The homelab in one picture

What a homelab actually is, and how my phone pulls a letter out of my archive at home while I'm on a train, without a single door open on the router.

October 29, 2026 · 12 min read

proxmoxdebiannetbirdadguardnginxproxymanagerpaperlessngxopentofuansible

Two paths through the homelabBlue: a request travels from the phone through an encrypted tunnel to the proxy in the network and access layer and on to the Paperless service. Violet and orange: a change goes into the pipeline via git push, a new VM appears on the foundation, and at night its backup travels encrypted to Hetzner.FoundationServicesNetwork & accessnew VMon the road① A requestphone → tunnel → proxy → service② A changepush → pipeline → VM → backupgit pushPipelineBackupevery night, encrypted

I’m on a train, and I remember the letter that arrived yesterday. What did it say again? I type paperless.home.example into my phone, and a second later there it is, scanned, with sender and date already filled in.

Any cloud app can do that, you might say. The difference is where the letter lives. It sits on a computer in my home. And my router, the little box that connects my home to the internet, lets nobody in from outside. There isn’t a single open door on it, or in the jargon, no open port. So how does a request from the train get in at all? And what happens if that computer dies tonight?

That’s what this series is about. This first part is the way in, including for you if you’ve never built anything like this. So we’ll start right at the beginning.

What is a homelab, anyway?

Most of the software we use every day doesn’t run on our own devices. Photos go to Google or Apple, files to Dropbox. Somewhere there are computers that run around the clock, waiting for requests. A computer like that is called a server, and a program running on it that you use through a browser or an app is called a service.

A homelab is the same thing in miniature, at home: a few computers running your own services, say your own photo library or your own archive for letters. You get to decide where your data lives and who gets to see it. The price is that the looking-after is on you too, from updates and backups to making sure nothing falls over at night. That looking-after is what this series is really about.

Why I rebuilt mine

Until September 2026 my services ran as a loose collection on my workstation, the computer I sit at every day. That works for a surprisingly long time. Right up until you reboot it, and name lookups for the home network, the document archive and the file storage all disappear with it.

When I rebuilt it, I wanted to do it properly, and first I had to write down what “properly” meant. I ended up with four goals, and every decision since has had to hold up against them.

GoalWhat it means in practice
Nothing reachable from the internetNo port is open on the router, no service has a public address.
Every service on its ownOne broken service doesn’t take another one down. Each can be backed up, restored and restarted on its own.
Everything reproducibleEverything can be rebuilt from a written description. Anything set only by hand counts as a bug.
Everything recoverableIf the computer fails, the backups live somewhere else, and I find out right away.

A house in four parts

What came out of those goals, I picture as a house. At the bottom is the foundation, the hardware. On top of it live the services. So that I can reach them from anywhere, and nobody else can, there’s network & access, think front door, doorbell and reception desk. And next to the house stands operations, a sort of caretaker who builds everything from a blueprint, backs it up every night and warns me when something is missing. The map below shows these four parts. It comes back in every part of the series so you always know where we are.

The four parts of the homelab: phone and laptop reach the services through network & access; the services run on a foundation of one Proxmox host with one VM per service; next to it, operations with code, pipeline, backup and monitoring
The series map: foundation, network & access and services make up the house; operations builds, tests, backs up and watches it. It will guide you through all six parts.

Let’s look at the two bottom layers first, since everything else rests on them.

The foundation: one computer, many VMs

The whole house stands on a single computer, which I call pve1. The hardware is humbler than you might expect: an AMD Ryzen 9 4900H, which is a laptop CPU with 16 threads, and 62 GB of RAM.

It runs Proxmox VE, software that splits one real computer into many small computers walled off from each other. Each of these computers-inside-a-computer is called a virtual machine, or VM. Think of an apartment block. Every VM is a flat with its own front door and its own operating system, and if one flat catches fire, the others stay dry.

That’s the second goal from above. Every service gets its own VM running Debian 13, a free Linux operating system. In total pve1 runs 17 VMs. Most are service VMs built from the same blueprint; the rest are older machines, such as a file server running TrueNAS and the home automation system Home Assistant. The separation isn’t free, because every VM needs its own memory. At 1 to 8 GB per VM, that’s a price I’m happy to pay.

I name each VM after its service and add a number. The VM for Paperless is therefore called paperless-01.

The services

The flats are home to the programs I actually use. Paperless-ngx is my archive for scanned letters, the service from the train. Nextcloud is my own file storage, much like Dropbox, and Immich my own photo library, much like Google Photos. With n8n I build automations along the lines of “when this happens, do that”. And Open WebUI is a chat window for a language model that runs in my home.

So much for the house. The real question from the train, though, is how my phone finds these services in the first place. To answer it we’ll walk through the house twice, once with the request from the train and once with a change made at my desk.

A private tunnel home

My phone is on mobile data, pve1 sits behind the router, and the internet lies in between. Since the router won’t open a door, there has to be another way in.

That way is called NetBird. NetBird lays a private network over the internet, known as an overlay network. Picture a set of private tunnels that only my own devices may drive through. The tunnels are built by a technology called WireGuard, and they’re encrypted, so anyone watching from outside sees gibberish. Every device in this network is called a peer, my phone just as much as every service VM.

For the peers to find each other there’s a go-between, the NetBird server. It runs on a small rented server at the hosting company Hetzner. It tells every peer whom it may reach, but the data itself never passes through it. Both ends build their tunnel from the inside out, which is why the router at home doesn’t have to open a door.

From a name to a page

The phone is on the private network now, but paperless.home.example alone doesn’t get it anywhere, because computers on a network find each other by numbers, not names. The picture shows the whole journey from the name to the finished page. The participants stand along the top, and the arrows follow time from top to bottom.

Sequence diagram: the NetBird server hands peers and a DNS rule to the phone; the phone asks dns-01 for paperless.home.example, gets the address of proxy-01 through the wildcard, talks HTTPS inside the WireGuard tunnel to the proxy, the proxy asks paperless-01 over HTTP inside the tunnel and returns the page over HTTPS
One request from the phone to Paperless. Every arrow except the first runs through a WireGuard tunnel.

The first arrow comes from the NetBird server. Among other things, it tells the phone whom to ask about names. That’s the job of a DNS server, the phone book of the network, which knows the number for every name. Mine is AdGuard Home in its own VM called dns-01, with a copy on a Raspberry Pi as a fallback.

AdGuard doesn’t even have an entry for Paperless. It knows a single placeholder rule, a so-called wildcard, which sends every name ending in .home.example to the same address. Waiting there is the reverse proxy, or just the proxy. It works like the reception desk in an office building: it takes every request, reads from the name which service it’s for, and walks it over. It lives in the VM proxy-01. Since every name leads to the proxy, a new service never needs a new phone book entry.

Now the phone opens a connection to the proxy through the WireGuard tunnel, using HTTPS, the encrypted language of websites that gives you the padlock in the browser. The proxy shows a certificate, a kind of ID card that proves to the browser it’s talking to the right server. How you get an ID card like that when nobody from outside can knock on your door is a story for part 2.

Finally the proxy hands the request to paperless-01, again through a WireGuard tunnel, even though both VMs sit on the same computer. My question from the train touches exactly three VMs, and none of them has an open door to the internet.

From my desk to a running VM

The second walk starts at my desk. Say I want to try a new service. Instead of clicking a VM together by hand, I write a short text file, a kind of blueprint, with the VM’s name, CPU cores, memory and disk size.

The file goes into a repository, or repo, a folder that remembers every change. Mine is hosted on GitLab. As soon as I upload a change, a git push in the jargon, a pipeline starts. That’s an assembly line of fixed steps that runs by itself after every change. The picture shows the assembly line from top to bottom, all the way into the first night.

Flow: git push starts the pipeline on my own runner with lint, plan and an apply triggered by hand; apply creates a Debian VM through the Proxmox API, Ansible configures it over SSH, the service runs; every night a VM backup at 01:00 and encrypted compose backups at 03:30 go to Hetzner, and on Sundays an update with a way back
From a file to a running service and into the nightly backup. Only the step outlined in red actually changes anything, and I trigger it by hand.

The assembly line runs on a runner, the machine that carries out the steps. My runner is itself a VM on pve1 and asks GitLab for work on its own, so this doesn’t need an open door either. First it checks the files, and a checker called gitleaks looks for passwords that slipped in by accident.

Next, OpenTofu, a tool built for exactly this kind of blueprint, compares the file with what’s actually running on pve1 and writes a plan of what will be created, changed or destroyed. This is where I deliberately stop and read. If the plan says “must be replaced” next to an existing VM, meaning that VM would be torn down and rebuilt, something is off, and I don’t press the button.

When I do press it, OpenTofu carries out exactly the plan I read, and Proxmox creates a new VM from a ready-made Debian image. On first boot a small start-up program, cloud-init, gives it only the bare minimum: a name, a user, and a key for SSH, the encrypted remote login. About 30 seconds later the VM answers.

An empty VM is like a freshly painted flat with no furniture. Ansible does the furnishing, a tool that logs in over SSH and works through a checklist. It installs updates, joins the VM to NetBird, sets up the backup and update schedules and installs Docker. Docker packs a program together with everything it needs into a sealed box called a container, and that’s where the service finally runs. Its passwords go over SSH into a .env file on the VM. They never touch the repo.

Finally the service gets an entry on the proxy, and Uptime Kuma, my watchman who keeps checking that every service answers, gets one more post to guard. From then on the service answers under its name, from the train as well.

From practice: The runner lives on one of the very VMs it manages, and that showed on moving day. A run that was only meant to change one harmless setting restarted several VMs, including the runner it was running on. What happened next, and which line has been in every blueprint since, is a story for part 5.

The first night

That leaves the second question from the start: what if pve1 dies tonight? Then the only thing that helps is a backup kept somewhere else. For that I rent two things from Hetzner. A second small server runs Proxmox Backup Server, a program that backs up whole VMs. And there’s object storage, online storage that works like a warehouse full of lockers, called buckets. Because of the language you use to talk to it, this kind of storage usually just goes by S3.

Three backups then run on their own:

WhenWhatWhere to
daily 01:00the whole VMProxmox Backup Server at Hetzner, datastore in S3; pve1 encrypts the data before it leaves
daily 03:30each Docker stack on its own, meaning all containers of one service togetherS3, encrypted with rclone crypt, file names included
Sundays 05:00container updateslocal: a backup first, then a message with the command for the way back

The buckets also keep old versions, so even an accidentally deleted state can be recovered for a while.

To make sure I notice when something goes wrong, there’s ntfy, a service for push notifications to my phone. The Sunday update reports its result there, and Uptime Kuma uses the same route when a service stops answering. ntfy runs on the backup server at Hetzner rather than on pve1, for a simple reason. If pve1 goes down, someone should still be around to tell me.

Everything in one picture

Now that every piece has a name, the whole system fits into one picture. The NetBird server and the phone are at the top, pve1 with its VMs in the middle, and the backup server and storage at Hetzner at the bottom. A few names in there, such as Authentik and InfluxDB, only come up in later parts.

Architecture: the phone gets peers and a DNS rule from the NetBird server at Hetzner, asks dns-01 for the name and talks HTTPS through the tunnel to proxy-01 on pve1; the proxy forwards to paperless-01 and the other services. A GitLab runner on pve1 builds the VMs. At night, VM backups go to the backup server at Hetzner and compose backups to object storage; Uptime Kuma alerts through ntfy on the backup server.
The system in one picture. Blue: network & access, green: services, purple and orange: operations. Dashed lines are control, backups and alerts.

Both of today’s walks are in there. The request from the train runs from the phone via dns-01 and proxy-01 to paperless-01; the change from my desk runs via the runner to a new VM, and at night on to Hetzner.

Back on the train

So the next time I pull up a letter on the train, two stories meet in the background. One is my request finding its way through three VMs without a single open door. The other is everything that had to happen beforehand so those VMs exist at all, and will still be there tomorrow.

Looking back, the four goals settled almost every later decision in advance. And two habits have stuck. The most dangerous step has a human in front of it who has read the plan, and backups and alerts live on different hardware from what they protect.

The other five parts follow the map. Part 2 stays with the phone and uses a packet capture to check whether “everything is encrypted” holds on my own home network. Part 3 follows the letter from the scanner button to a searchable document, filed by a language model running at home. Then we move to the desk: part 4 shows how the blueprint becomes a running service, and part 5 is about my own runners and a nightly test suite that suddenly ran too slow although nothing was wrong with the code. Part 6 finally returns to the night the computer dies.