Not long ago, my home server I set up in 2020 died. It was time to rebuild it, but (much) better. Here’s what I did:

The rackmount home server

Hardware

I used the same hardware I had been using previously: an old Thinkstation S30 workstation. This time, I stripped it out of the factory case and reinstalled it in a compact rackmount 3U server chassis. This setup has two Seagate IronWolf 4TB NAS disks set up with software RAID 1 for redundancy and an SSD for the operating system.

I wall mounted a 6U network rack and slid the server in to occupy the bottom 3U.

The top 3U are occupied by a Cyberpower 300W UPS, a patch panel with an unmanaged switch behind it, and brush passthrough panel to run ethernet through.

Software

The home server runs Ubuntu Server. On it, I installed Docker. Then, I created a GitHub repository with a Docker Compose file. In that Docker Compose file, I set up a few containers (with their appropriate mounts):

  • kaive/docker-web-gui: a simple GUI app for viewing Docker containers and logs
  • nextcloud: my personal storage
  • caddy: a simple reverse proxy for routing requests to containers
  • jpillora/dnsmasq: a DNS server for my local network ()
  • hwdsl2/ipsec-vpn-server: a VPN server for remote access
  • ghcr.io/akarys42/cloudflare-ddns-docker: to update my Cloudflare DNS records when my IP changes
  • homebridge: a bridge for my non-HomeKit devices to HomeKit
  • backup-to-s3: backs up nextcloud to S3 on a regular basis
  • grahamleslie/docker-host-sensor-page: monitors the heat of the home server

I also created a few networks to isolate containers. In particular, I networked the DNS container and VPN container together with static IPs to solve a big gotcha: the VPN container needs an environment variable VPN_DNS_SRV1 set with the static internal IP of the DNS container to resolve DNS requests in the home network.

I also added a script to generate an adblock list to the repo. I modified it to output to a file called ads.hosts.

I included a simple dnsmasq.conf to configure dnsmasq and mounted it to the dnsmasq container (along with the adblock list):

log-queries
no-resolv
server=1.1.1.1 # Use Cloudflare DNS.
server=1.0.0.1
strict-order
address=/home/192.168.86.2 # Route all requests ending in `.home` to my home server.
addn-hosts=/etc/ads.hosts # Include the adblock list.

To host a DNS server in Ubuntu Server, you need to modify /etc/systemd/resolved.conf to disable the default listener on port 53.

Last, I created a Caddyfile to configure caddy. It just has an entry for each domain (example: nextcloud.home) and routes it to the correct port exposed by the container.

Docker

I then cloned that repo to the server, created some .env files to include the secrets for each container, and ran the adblock list generator to create the file that will mount to dnsmasq. I can just run docker-compose up -d and everything starts up.

The last step was to set a static IP for the home server and forward the necessary ports for external access. A simple home server with a configuration in GitHub–easy to rebuild if something goes wrong.