Craig Wright
HomeAboutProjectsHome labMusingsContact
© 2026 Craig Wright

Home lab

A thin client, an 8TB drive and far too much curiosity. It runs my photos, music, passwords, smart home and this website, and these days it's built to survive its own failure.

Home network

Internet

Full-fibre

Router

VPN in · forwards no ports

Dell Wyse 5070

A thin client: 4-core Celeron, 16GB RAM, Intel UHD 600. Silent, and sips power

512GB SSD

Ubuntu Server, and every container's own state

8TB USB drive

The media library, the photo library, and this site's files

MusicFilms and TVPhotos

The public

Four websites, and nothing else

A visitor

Anyone, anywhere

▼
HTTPS

Cloudflare

Public DNS, TLS, the edge the internet actually talks to

▼
down the tunnel

Tunnel connector

Runs on the server and dials out. It never listens for anything

A night in the life

Server time (UTC). Marked ones run on Sundays.

  1. HourlyDrive health

    SMART readings from both drives, compared against each drive's own baseline

  2. 01:45Config export

    Every compose file, crontab and setting, secrets stripped, checked against git for drift

  3. 02:00Offsite backup

    Encrypted, deduplicated, to Backblaze B2, at the lowest disk priority so nothing stutters

  4. 03:00Updates
When to replace a drive: buy-now and plan-one triggers for the spinning disk and the SSD
The replacement triggers, written down in advance.

What’s public, by hostname

  • wanating.ukThis site

    This personal site.

    Visit ↗Learn more ↗
  • armarium.appThis site

    Armarium marketing site.

    Visit ↗Learn more ↗
  • daegwork.appThis site

    Dægwork marketing site.

    Visit ↗Learn more ↗

Mesh Wi-Fi

2 nodes

Ubuntu Server

Dell Wyse 5070 · Docker · 8TB · BLE

Trusted network

Gaming · media · workstation (wired)

Smart home Wi-Fi

Cameras & IoT, isolated

What it hosts

Docker stacks · tunnel dials out

Wireless backhaul
Perimeter firewall
Host firewall
Wired / logical Wi-Fi Firewall checkpoint Home network boundary

Scroll to see the full diagram →

Audiobooks

Network plumbing

  • Pi-hole: DNS for the whole house, and the home of every private name
  • dnscrypt-proxy: Pi-hole's only upstream, so lookups leave encrypted
  • Nginx Proxy Manager: HTTPS in front of every private service
  • cloudflared: The outbound tunnel for the public sites

This site

  • Public site: What you're reading
  • Admin console: Private, and a separate app
  • Data service: Polls everything, remembers history
  • Postgres: Where that history lives

Home and media

  • Home Assistant: Lights, climate, sensors, cameras
  • Plex: Ripped CDs, DVDs and Blu-rays, transcoded on the iGPU
  • Immich: Every photo, off iCloud and onto my own disk
  • Audiobookshelf: Audiobooks, with progress synced across devices
  • Vaultwarden: Passwords, self-hosted

Keeping it alive

  • Beszel: Resource history for the host and containers
  • autoheal: Restarts a container that fails its health check
  • Host scripts: Backups, drive health, config export, restore tests
  • Camera drop: The front door camera's snapshots land here

Every one of these is a Docker Compose stack in its own folder. The host itself runs very little else.

▼

The public site

wanating.uk and three product domains. Read-only, holding no secrets

Me

Everything else, private by default

My phone or laptop

On the sofa, or on the other side of the country

▼
VPN

The router

The only way in. It forwards no ports at all

▼

Pi-hole

Private names that only exist inside the house, ad blocking, encrypted DNS upstream

▼

Reverse proxy

One wildcard certificate, so even private pages are proper HTTPS

▼

Everything else

The admin console, smart home, media, photos, passwords, audiobooks, monitoring

The router forwards no ports, so the public side only exists because the server opened a connection outward itself, and that connection only leads to the public site. The one thing that crosses over is my phone’s location report: the public site hands it straight to the admin console, which does all the checking. The admin console itself isn’t in the same container, let alone on the same route.

Sun

Every container except DNS, with the old image IDs written down first

  • 03:45Reboot, if needed

    Only if an update asked for one, and never mid-backup

  • 05:00Drive self-testsSun

    A short test weekly, a full surface scan monthly

  • 06:30Restore testSun

    Restores every config backup to a scratch folder and checks it's really there

  • If it died tomorrow

    The rebuild, in the order the runbook insists on.

    1. 1

      Fresh disk, fresh Ubuntu

      From a USB stick

    2. 2

      Pull the config back

      With a read-only key that can't damage the backup

    3. 3

      DNS first

      Everything else resolves through it

    4. 4

      Proxy and tunnel

      The front doors, public and private

    5. 5

      Apps and data

      Each stack restored from its own backup

    6. 6

      Check it's all back

      A checklist, not a feeling

    Target: back within a day or two, losing no more than a day. Films and TV aren’t in the offsite backup on purpose. The discs are on the shelf.

  • core.wanating.ukThis site

    Wanating CMS Core marketing site.

    Visit ↗Learn more ↗
  • The shape of it

    A full-fibre line comes in through the router, which also does the firewalling. It's closed by default, and as it turns out I haven't opened anything (more on that below).

    A two-node mesh covers the house for Wi-Fi. Anything that can be wired, is. The server, my gaming PC, the media streamers and the workstation where most of my real work happens all sit on a wired backbone instead of competing for radio. Smart-home devices, cameras included, get their own Wi-Fi, away from everything else. A camera doesn't need to phone home to a vendor's cloud to be useful to me.

    Most people have a workshop. Mine is a small, silent box running Ubuntu, with a big external drive plugged into the back.

    It started the way most of my projects start: as a place to find out how things work. Game servers on a spare PC in my bedroom. Then websites for the communities I played in. Then whatever I was curious about that month. It's still that. I build things here, break them, and rebuild them properly once I understand why they broke.

    But somewhere in the last year it stopped being a toy. It now holds every photo I've taken, my whole ripped CD collection, my passwords, the smart home and the website you're reading. Every DNS lookup in the house goes through it.

    So the question has changed from "can I get this working?" to "what happens when this breaks, and would I even know?" This page is my best answer so far.

    The box

    The server is a Dell Wyse 5070. It's a thin client: the small, fanless box that sits on an office desk and shows someone else's computer on a screen. It was never meant to be a server, which is exactly why I like it. A four-core Celeron, 16GB of RAM, no fan, and very little power drawn for something that never switches off.

    It isn't powerful, but it doesn't need to be, because almost everything it does is waiting: for a request, for a sensor to change, for 2am. The one heavy job, transcoding video for Plex, goes to the small Intel GPU built into the chip, which does it far better than the CPU could.

    Two drives. A 512GB SSD holds Ubuntu and every container's own state. An 8TB USB drive holds the media, the photo library and this site's files. Everything runs in Docker, one Compose stack per folder, so each service can be understood, updated, backed up and rebuilt on its own.

    Two ways in, and only two

    This is the decision everything else rests on. The router forwards no ports at all. The one way in from outside is the router's own VPN, and it wants credentials before it says anything useful.

    So how is this website public? The server runs a Cloudflare Tunnel connector, which dials out to Cloudflare and holds that connection open. Cloudflare handles the public DNS and the certificates, and sends requests for wanating.uk (and the three product domains that share its app) back down the tunnel. The tunnel leads to exactly one thing: the public site.

    Everything else, I reach over the router's VPN. Once I'm on it, Pi-hole answers for private names that only exist inside the house, with no public DNS records at all. A reverse proxy puts every service behind proper HTTPS with one wildcard certificate. Plex's own remote access is switched off, so even films on the train come through the VPN.

    Home Assistant used to be the exception. It had a public hostname so the phone app could report my location from anywhere. Once my own companion app took over that job, the public route went, and Home Assistant went private like everything else. It's one less thing I have to think about.

    Security, the boring way

    Nothing clever here, just a few dull rules I try to stick to everywhere:

    • Nothing is exposed unless it has to be. Four websites face the internet. That's the whole list.
    • The public site knows nothing. Its container gets four environment variables, and the only secret among them is a token that can record page views and nothing else. It has a read-only view of the files it serves, a read-only filesystem, a non-root user, and no admin code at all.
    • The public site can't see the house. It has no port of its own. It sits on a private network that only the tunnel can reach, so if someone broke into it, there'd be nowhere to go: no route to the LAN, the router, the other services or even the internet. Its one lookup, the visitor's country for region-locked share links, comes from a header only Cloudflare can set.
    • The admin side is somewhere else entirely. It used to live at /admin inside the public app, with only a login between it and the internet. One wrong tunnel rule and it would have been exposed. Now it's a separate app, on a separate port, that the tunnel has never heard of. Logging in takes a password and an authenticator code, or a passkey. Changing anything about how I log in makes me prove it's me all over again.
    • Backups are written from the host, not from the apps. The key that can write to the offsite backup lives only in a script on the host, never inside a container. Browsing old backups from the admin console uses a second key that can only read.

    In September I sat down and audited the whole thing as if it belonged to someone I didn't trust. It was humbling. The most embarrassing find: the internet-facing container had been handed every secret on the server (the session key, the backup keys, the database password), because it loaded the same settings file as everything else. It never read any of them, but it had them all the same. Now each container gets a file with only its own keys, and the public one gets an explicit allowlist of four.

    Knowing before it breaks

    Backups help after something's gone wrong. I'd rather know before.

    Both drives are checked every hour. The media drive is healthy, but it parks its heads nearly 30 times an hour, a very keen power-saving habit of this particular WD drive, and it's done that over 335,000 times. It's rated for at least 600,000. I tried to calm it down and found the USB enclosure won't pass the right commands through to the drive. My first thresholds were twitchy: a warning at 450,000 and "buy a new drive now" at 550,000, before it had even reached its minimum rating. Head parking is wear, not a sign of failure, so now it's a note at 600,000, a nudge to plan a replacement at a million, and never a reason to panic-buy.

    The SSD taught me something about alerting, too. The first version warned whenever that error count was above zero. That meant a permanent warning, one I'd soon have learned to ignore, and it would have hidden the one change that mattered. Now each drive has a baseline, and the alarm is for new damage. Each kind of drive has a written list of replacement triggers, in two strengths: "buy now" for a drive that's actually failing, "plan one" for one that's just wearing. That way the decision gets made calmly, rather than at midnight.

    Beyond the drives, an outside service expects a ping from the server every five minutes and tells me if it stops. If the house goes quiet, something outside the house notices. A watchdog restarts any container that fails its own health check. Updates run weekly rather than nightly, so a bad release gets a week to show itself, and the old image IDs are written down first so rolling back is quick. The exception is DNS. Pi-hole is updated by hand, because when it goes down, the whole house notices.

    All of it rolls up into the admin console behind this site, and into a widget on my phone's home screen. I tend to hear about a problem before I'd have gone looking for it.

    Drive health: SMART readings for the media drive and the SSD, with the SSD's old errors marked unchanged since the baseline
    Drive health, checked hourly. The SSD's old errors are there, and flagged as unchanged.

    Then I asked: what if it died?

    For years my disaster recovery plan was "I could rebuild it." Then I looked at the SMART data for the SSD that holds every container's state. At some point in its past, it had logged 21 uncorrectable errors and 40 unreadable sectors. It isn't failing now, and nothing has changed since I started watching. But an SSD with that history can go without warning, and I realised "I could rebuild it" wasn't really a plan.

    So now there is one:

    • Every night at 2am, restic sends encrypted, deduplicated snapshots to Backblaze B2, at the lowest disk priority so nothing stutters. Each thing gets its own tag and its own restore command: this site, the passwords, the music, the audiobooks, the photo library.
    • Each app's settings are backed up separately, using the app's own export where it has one: Plex's watch history but not its gigabytes of cache, Pi-hole's own backup format, Home Assistant's config without its history database. They're small, and they're the part you can't recreate from memory.
    • A runbook covers a blank disk: every step from a fresh Ubuntu install to everything back, including the disks, the firewall rules, every Compose stack, both crontabs, the settings that only live in apps' web pages, and the order to bring it all back (DNS first, because everything depends on it). To test it, I gave it to an AI agent with no other context and had it rebuild the server in a virtual machine. It found the gaps I couldn't see, because I already knew the answers.
    • On Sunday mornings a script restores every config backup to a scratch folder and checks it's really there. I didn't really trust a backup until I'd restored one.
    • Every night the server exports its non-secret configuration (compose files, crontabs, firewall rules, app settings with tokens stripped) and compares it with a copy in git, so if I change something on the box and forget to write it down, I find out the next morning.

    Some things are left out on purpose. Films and TV aren't in the offsite backup: the discs are on the shelf, and re-ripping is cheaper than storing terabytes in the cloud. Home Assistant's sensor history isn't either. I'd miss it, but not enough to pay to store it.

    Server backups: five offsite backups, each with its size, last run and a Healthy status
    The nightly offsite backups, as the admin console shows them.

    What it's actually for

    It's easy to write about the plumbing and forget why it exists.

    Every photo I've taken now lives in Immich on my own disk instead of someone else's cloud. Moving over a decade of them out of iCloud was a project in itself, and the part I'm proudest of is the boring bit at the end, restoring them from the offsite backup to prove it all worked.

    My CD collection is fully ripped, with the discs kept alongside (the same physical-plus-digital thinking behind Armarium), and Plex serves it. Audiobookshelf does the same for audiobooks and keeps my place across devices. Home Assistant runs the lights, climate and sensors, and drives the cameras' privacy mode, which switches them off when someone's home.

    And then there's this website and the private console behind it, which has grown into its own story: how this site works.

    What hasn't changed

    It's still a workshop really, not an enterprise environment and definitely not a hardware showcase.

    What has changed is that I now build as if it's going to fail, because one day it will. Next on the list is a bigger drive for the media, with the current one becoming a second local backup, so the important things exist in three places.

    The hardware will change again, and so will the software, and I'll probably still be poking at it.