Home Server
An old laptop at home that runs my password manager, music, files and ad blocking for the home network.
I run the services I use every day on an old laptop at home with Ubuntu Server. It keeps my passwords, my music and my files, and it blocks ads for every device on the home network. I reach it from anywhere over Tailscale, a VPN that puts my devices on one private network.
What runs on it
| Service | What I use it for |
|---|---|
| Vaultwarden | Passwords, synced to the Bitwarden apps and browser extension |
| Navidrome | Streaming my music library to my phone |
| Nextcloud | Files, like a self-hosted Google Drive |
| Pi-hole | DNS and ad blocking for every device at home |
| Homepage | One dashboard with a link to each service and its status |
| Uptime Kuma | Checks every service and messages me on Telegram when one goes down |
One Docker Compose file in a git repository defines all six containers. Git holds only the configuration. The data lives on the server and in the backups.
Backups
Every Sunday at 2 a.m. a script copies the data to a USB drive. The password and file databases are SQLite files that the services write to all the time, so a plain copy can catch one halfway through a write. The script takes a snapshot with SQLite’s online backup instead. Every run also pings Uptime Kuma. If a backup fails, or no backup arrives for about eight days, Uptime Kuma sends an alert to Telegram.
The backup is one copy on one drive, plugged into the server itself. An offsite copy is the next step.
What broke
I worked through each of these with Claude Code, an AI coding assistant, over SSH.
The empty password vault. One day in September the Bitwarden app on my phone showed no passwords. The data on the server was fine. The app had updated itself to a version that needs a newer Vaultwarden. The server still ran the image I had pulled in April, because a :latest tag only changes when you pull it again. Pulling the new image and logging in again brought the vault back. Nothing updates the server automatically, so the mismatch will come back unless I update it when the apps update.
Updating all services at once. Running docker compose pull for the whole stack would have moved Nextcloud forward several major versions in one step. Nextcloud refuses to skip a major version, so it would have stayed down. Now I update one service at a time and keep a copy of its database and old image first. Nextcloud goes up one major version at a time.
The home internet outage. My devices on Tailscale send their DNS lookups to Pi-hole at home. When the home connection dropped, my laptop on a phone hotspot could not look up any name. It looked as if Tailscale had broken the internet. A public DNS server as a fallback would let ads through. Tailscale asks all its DNS servers at once and uses the fastest answer, so the public server would answer some lookups first. Until there is a second resolver, sudo tailscale set --accept-dns=false turns off Tailscale’s DNS on that device and gets name lookups working again.
The backup drive after a reboot. The kernel log after one reboot showed the USB drive mount and then drop off the USB bus 47 seconds later. It came back as a new device, and nothing mounted it again. Without a check, the next backup would have written into an empty folder on the laptop’s own disk. The script checks that the drive is mounted before it copies anything, so a backup in that state stops and sends the alert.