Homelab.
A lab on two Proxmox machines at home, where I learn infrastructure on services I actually use. It is reachable from outside without a single open port.
The Proxmox panel and real command output from my cluster, 5 November 2026. Hidden on purpose: one private container, the node addresses, the cluster name, the signed-in account and the task history.
0 open ports on the router
2 Proxmox nodes in one cluster
9 LXC containers, 6 of them running
Why I run it.
I wanted to learn infrastructure on services I actually use. It runs at home on two Proxmox machines, and the router forwards no ports at all: everything goes out through Cloudflare Tunnel.
Every decision is written down with the reason behind it and what would change it. When something breaks, the symptom, the cause and the fix go in a log, checked against the live machines rather than my memory.
What runs there.
Read from the cluster on 5 November 2026. Containers that are stopped are left out.
Hardware
- Node 1
- Intel i3-4130T, 8 GB of RAM and a 238 GB SSD. Everything runs here today.
- Node 2
- Dell OptiPlex 7050, in the cluster since 1 October 2026. No containers on it yet.
- Proxmox VE 9.2
- Runs both machines as one cluster.
Services
- Clickwill staging
- The Clickwill website, tested here before it goes live.
- Clickwill dashboard
- Downloads, site traffic and search terms.
- Preview server
- Caddy. Drafts of my websites on a public link, kept out of search engines.
- File storage
- Nextcloud on an 80 GB disk.
- Docker and Portainer
- Smaller tools, including a benchmark panel for local AI models.
- My most sensitive app
- Private, with stricter rules than the rest of the lab.
Access
- Cloudflare Tunnel
- The only way in from outside, over outbound connections from my machines.
- Cloudflare Access
- Private hosts ask for a one-time code sent by e-mail.
- WARP and Gateway
- The sensitive app opens only from a registered device.
How I work with AI agents here.
Agents do much of the hands-on work, and their access is wide. That is a trade-off I keep on my list.
What the agents do.
Hosts and containers
Deployment, configuration and diagnosis, over SSH.
Cloudflare
DNS, tunnels and Access rules, over the API.
What I do.
Decisions
What runs, what is exposed and which risks I accept.
Dashboards and installers
Anything in a browser panel, or that needs sudo on my Mac.
Private keys
They stay with me.
Decisions I wrote down.
No open ports.
A scan of my public address finds nothing, and no port can be left open by accident. Only a service that cannot go through a tunnel would change this.
Protected by default.
A wildcard rule covers every new host from the moment it exists. An exception takes a deliberate step, not the other way round.
The keys go into the backup.
A backup without its keys is useless once the container is gone. The archive is encrypted with a public key whose private half is not on the server, so the server can make a backup but cannot read it.
A copy off the server.
The nightly copies sit on the same disk as the app. A failed disk is more likely than a fire, so the latest copy is pulled to my Mac.
What broke, and what I changed.
18 Aug 2026
Half of all requests returned 502.
It looked like a network fault. The tunnel had two connectors, one on the server and one on my Mac, and the Mac had left home. It cost me an hour, and it is now the first thing I check.
29 Aug
Hardening that existed only on paper.
Without nesting=1 in an unprivileged container, systemd could not build its sandbox and the units failed. The security plan relied on exactly those settings. Unnoticed, they would have existed only in a config file.
5 Sep
A lockfile from my Mac took a service down.
package-lock.json from macOS forced a compile in a container that deliberately has no compiler. The lockfile is now left out of the deploy package.
5 Sep
A header that got round a limit.
Express read the client address from the leftmost X-Forwarded-For entry, so a logged-in user could rotate it to avoid the PIN attempt limit. The address now comes from Cf-Connecting-IP.
6 Sep
A read-only security audit.
Of the whole setup, with the evidence saved. The private app held up much better than the host around it.
1 Oct
A second node.
The Dell joined the first machine in one cluster, and both moved to Proxmox 9.2. Services came back without damage.
19 Oct
A 302 proved nothing.
During an outage every host still answered 302 to curl while the browser showed Error 1033. Access replies before a request reaches the tunnel, so I now test through the one public host.
What is still open.
The lab is not finished, and this is the list.
Backups for the rest
Only the sensitive app is backed up, with a snapshot every night. The rest of the lab has no copies yet.
Containers on node 2
The second machine is in the cluster but still empty.
Agent access
Cutting it back means more manual work for me. That decision is still open.
Networking and Kubernetes
What I want to learn next.
- Runs on
- Proxmox VE 9.2, two nodes
- Access
- Cloudflare Tunnel, Cloudflare Access, WARP
- Built with
- LXC, Docker, Caddy, Nextcloud




