I built my homelab on a $200 Dell OptiPlex
I started with a Dell OptiPlex off Kijiji and four enterprise drives from eBay. The setup grew to 20+ Docker services, a VPS reverse proxy, a mesh VPN and a job-search pipeline, and this post covers how it came together.
Update, September 2026: I retired the job-search pipeline mentioned here after I started at UofT Libraries. The post describes it as it ran.
I bought a Dell OptiPlex 7060 off Kijiji for around $200, a basic office PC with no storage drives in it. I added more RAM, threw in four used enterprise hard drives from eBay, and started throwing services at it.
That was last year. It now runs twenty-something services and exposes eight websites publicly. A private network connects it to a remote GPU machine, and it runs a job-search pipeline that scored 633 listings with an AI model. This post covers the hardware, networking, and stack. The next one covers the pipeline.
The hardware
The Dell OptiPlex 7060 is a small office desktop that companies cycle out on a fixed schedule regardless of condition. These are everywhere on the secondhand market. The CPU is fast enough for server work. I upgraded the RAM to 40 GB.
For storage, I added four 9.1 TB enterprise hard drives from eBay. The logic was the same as with the PC: data centers decommission these on a schedule rather than because they’re failing. They’re cheap and have a lot of life left. I set them up as RAID 10, a configuration that mirrors and stripes data across all four drives so you can lose any single one without losing anything. That gives me about 18 TB usable.
One of the four has a pending sector I’m watching. The other three are fine. I added drive health monitoring months in; I should have set it up from the start.
The networking
Nexus doesn’t have a public IP. It sits behind my home router with no ports forwarded. To expose services publicly, I use a free Oracle Cloud VM as a reverse proxy. Traffic from the internet hits that VM first, and Nginx (a web server acting as a traffic director) forwards it to Nexus over an encrypted private network.
That private network runs on Tailscale, which connects my machines as if they were on the same local network even though they’re in different locations. My laptop, the VM, and a Windows machine I use for AI workloads are all on it. It needs no configuration and no static IPs, and it works from anywhere.
The VM handles SSL (the padlock in your browser) via Certbot and routes eight subdomains to services running on Nexus.
The stack
Everything runs in Docker, a system that lets you run applications in isolated containers so they don’t interfere with each other and are easy to update or restart. Here’s what’s running:
Storage and apps:
- Gitea: self-hosted Git repository (like GitHub, but on my own hardware)
- Immich: photo library to replace iCloud
Monitoring:
- Prometheus + Grafana: Prometheus collects metrics like CPU, memory, and disk usage. Grafana reads from it, and building its dashboards is still on my list.
- Loki + Promtail: pulls logs from all containers into one searchable place
- Scrutiny: watches hard drive health using the drives’ built-in self-reporting
Security:
- CrowdSec: reads incoming traffic, identifies attack patterns, and blocks malicious IPs. It also shares threat data worldwide, so if someone is attacking servers in Germany, your setup knows about them before they try you.
- Wazuh: watches the host system from the inside for suspicious activity such as failed logins, unexpected file changes and unusual processes
Pipeline:
- Flask + SQLite job-search dashboard (more on that in the next post)
On the security stack
Running both CrowdSec and Wazuh is probably overkill, but they watch different things. CrowdSec sits at the network edge and blocks bad traffic before it reaches anything. In the first 90 days it blocked over 100 intrusion attempts, most of them bots hammering the login pages of anything they can find. Wazuh watches the machine itself. Running both means you catch things that fall through either one alone.
One thing I didn’t expect: CrowdSec flagged my own uptime monitoring tool as an attacker. I use Uptime Kuma to periodically check whether my services are up, and it does that by making repeated requests to them. From CrowdSec’s perspective, that pattern looks identical to a scanner probing for vulnerabilities. I spent a while confused about why my services were showing as unreachable before I made the connection.
The obvious move is to manually unban the IP, but that doesn’t fix it. The ban comes back the next time Uptime Kuma runs its checks. The real fix is a whitelist file that tells CrowdSec to skip detection entirely for that source, before it even evaluates whether the traffic looks suspicious.
A few loose ends
If I were starting over: do the RAID setup before installing the OS. I set it up while the system was already running and it was annoying to sequence around. I’d also add drive health monitoring from day one rather than adding it months later.
The single Docker Compose file for twenty-plus services is also a problem, because restarting the monitoring stack means restarting everything. Splitting it into separate files per stack is on the list.
The next post covers the job pipeline running on top of this.