Blog / Rainhold, Second Generation Server

Date
July 7, 2025
Tags

Early days

I started self hosting so I could grind coffee for my wife without getting out of bed. Two years later I’m running an eighteen-role Ansible playbook, hosting nearly as many services, and building multiple custom Docker images to meet my specific needs.

Also, I don’t drink coffee.

In my house you’ll find a four bay Synology NAS and a tiny Lenovo workstation plugged directly into a mesh router. The Lenovo runs Proxmox, which hosts VMs for Debian and Windows. Home Assistant hums along on a Home Assistant Green.

I called my first attempt Docker Symphony. It ran on an Ubuntu VM and required a lot of manual care. I did all updates by hand. The entire VM disk image was backed up to the NAS each week. Not fun.

A futuristic dystopian Seattle, with an Ansible Space Needle and a Docker whale in the harbor

Rainhold

This modern version is called Rainhold because I live in Seattle and daydream about being in Chasm City.

Its best features:

  • Users are stored in an LDAP service via the delightfully minimal LLDAP, then synced to Pocket ID
  • Jellyfin, Gitea, and Grafana have Pocket ID’s OAuth define its users and groups
  • Pocket ID only works with Passkeys. I’m a believer. They’re great.
  • TinyAuth, secured with Pocket ID, sits between sites I want exposed on the public Internet but only visible to my users
  • Ansible playbooks set up everything. Once you have a Linux user who can sudo and have filled out the variables file, you’re good to run the playbook.
  • All email sent from the server is captured and sent to a local mailbox accessible privately via IMAP
  • Backups of all Docker volumes is scheduled to run daily. I have tested the restore script. It works.

Public sites

Public sites are served via:

  1. Cloudflare Tunnels
  2. Caddy
  3. Docker container

Private sites are only available on my Tailnet via:

  1. Dummy DNS entries
  2. NextDNS
  3. Tailscale
  4. Caddy
  5. Docker container

While I pay for NextDNS, I don’t rely on any special features in Cloudflare or Tailscale. When they’re ready to turn the screws on us, I’ll move to a cheap VPS and Wireguard.

Ansible

I used ChatGPT and Copilot to learn Ansible and debug the myriad issues as I tested the playbook. Discovering Ansible was a “where have you been all my life” moment. It automates everything about the server setup: unattended upgrades, changing passwords, SSH hardening, setting up Cloudflare, writing all the Docker environment files.

I don’t have to back up the entire server anymore, just the Docker volumes. I can recreate the whole VM with a single YAML file.

Custom Docker images

I ran into a couple scenarios that required building tiny Docker images:

Email

Now that SendGrid is free with trial rather than free with low usage, I needed a way to get email from my services.

  • Complete email in a box solution
    Nope, way too much trouble to ensure reliable delivery. Plus I didn’t want to get Xfinity mad at me.
  • Postfix to Mailrise to Apprise to Mastodon
    Nope, you can only get the first 500 characters of plain text email, even if getting all that working was pretty fun.
  • Bespoke Postfix to Mailrise to Email Page to Mailrise to Apprise to Mastodon
    Nope, that’s even more convoluted! To be fair, it worked pretty well, and I had high hopes until I realized there was no way to (a) get the to and from addresses, and (b) receive HTML email from a POST request.

Finally I landed on the actual simplest option: unauthenticated Postfix that sends all mail to a single mailbox that’s served privately on the Tailnet by Dovecot. I can only check the mail on my desktop mail app, but I’ll take it.

Enjoy

Writing technical documentation is one of my passions. I spent a lot of time figuring out how to get LDAP, OAuth, and GitLab/Gitea’s runners working. AI was helpful, but some things have to be done manually. I hope you find Rainhold’s docs folder useful in your own journey.