Home Lab setup
January 01, 2024I recently picked up a used Dell OptiPlex 3070 to replace my aging Pi, and went down a (fun!) rabbit hole of automating provision/management using a combination of Ansible and Docker Compose.
Setup #
I started fresh from a debian-standard ISO image and configured the net interface.
Docs were consulted heavily (thank you Archwiki/Debian docs), as it has been
awhile since I did this! Turns out the ethN
etc. interface names are all
deprecated, and interfaces now have more unique names. I had to dig around to
figure out what to use, via:
1$ ls /sys/class/net
That gave me enp1s0
, which I stuck into /etc/network/interfaces
:
1auto enp1s0
2allow-hotplug enp1s0
3iface enp1s0 inet dhcp
And with that, I now have a working network connection. The next step was
setting up SSH keypair access (ssh-keygen
+ ssh-copy-id
), and then I let
Ansible take over.
I use an Ansible playbook to automate the server config. It does the bare host-system setup: timezone/NTP, disables IPv6 (because my ISP doesn't support it... in 2024; though I could just easily use it on my internal network), installs Docker, etc.
All services are managed through Docker Compose that gets invoked via Ansible.
Management #
Everything is managed through Ansible. Services are containerized and managed by Docker Compose (also via Ansible). This means that the directory structure looks like:
./ansible/
- Ansible-related manifests./services/<service-name>
- Service-related files, i.e.:- A
docker-compose.yml
file - manages/runs the service(s) - An Ansible
playbook.yml
file - deploys the service - Any service-related files (e.g. configuration)
- A
You can find the Ansible task responsible for provisioning a service here.
Reverse Proxy & DNS #
I use nginx as a reverse proxy to expose services over my internal network through the following domains:
gallifrey.sh
- Homedns.gallifrey.sh
- DNS/PiHoleunifi.gallifrey.sh
- Unifi Controller- etc.
I also run PiHole. It comes with dnsmasq
that I leverage as an all-in-one DNS
solution. Each DNS record is set up via Ansible tasks in
./services/pihole-and-dns/playbook.yml#L24-L50
.
You can also do this via PiHole's UI under Local DNS > DNS Records, but I prefer keeping mine programmatic and idempotent.
Unblocking iCloud Private Relay
PiHole blocks iCloud Private Relay by default. You can unblock it by setting
BLOCK_ICLOUD_PR=false
in its FTL conf file. I did it via an Ansible task
here.
SSL via LetsEncrypt #
I registered the domain gallifrey.sh
and stuck it on Cloudflare, then used
Certbot to auto-provision LetsEncrypt certs via Cloudflare DNS challenge. This
means I get free SSL over my internal network without having to deal with any
self-signed certs.
You can find my certbot command in services/nginx-certbot/docker-compose.yml#L12-L21
.
Spinning up new services #
Since everything's managed via Ansible, adding a new service means creating a
new directory under ./services/<service-name>
,
tossing in a docker-compose.yml
, and an Ansible playbook to deploy it.
Check out these commits to see how I deploy an observability stack using Grafana, cAdvisor, NodeExporter, and VictoriaMetrics:
34e31b7
service(observability): Deploy sets up the core servicesd710f65
service(observability): Setup DNS + r-proxy sets up the URLobservability.gallifrey.sh
After the two commits above, I run the following playbooks:
1# Deploy observability stack 2$ ansible-playbook \ 3 -i ansible/inventory.yml \ 4 services/observability/playbook.yml \ 5 --ask-become-pass 6# Update nginx conf 7$ ansible-playbook \ 8 -i ansible/inventory.yml \ 9 services/nginx-certbot/playbook.yml \ 10 --ask-become-pass 11# Update DNS 12$ ansible-playbook \ 13 -i ansible/inventory.yml \ 14 services/pihole-and-dns/playbook.yml \ 15 --ask-become-pass
Et voila:
Improvements #
- The Ansible stuff is rather repetitive. I guess that's YAML for you - I'm sure I can cut down on some of the repetition, but that's a rabbit hole for another day.
- Make a "deploy all" Ansible playbook? The reverse proxy + DNS stuff is repetitive and running three playbooks to deploy a single service is a bit jarring.
- Logs! I should set up something to collect logs from all my services.
- More services! Now that my Pi's spec is no longer a limiting factor, I could toss in Jellyfin/Sonarr etc. and have everything running smoothly.
- Separate DNS/PiHole using my old Pi? A single host means a single point-of-failure, and DNS is more critical than others.
- Figure out a better way to provision services? I'm not a fan of wrangling YAML in general.
You can find all of my configuration in this repository: half0wl/homelab.