Website Migration

From GitHub Pages to self-hosted TrueNAS infrastructure

December 2025 nginx + Cloudflare Tunnel

Overview

In December 2025 I moved nbnstack.com off GitHub Pages and onto my TrueNAS server. I wanted to control the headers, caching, and deploys myself, and to have the site covered by the same alerts as everything else.

The site runs in an nginx container. A Cloudflare Tunnel makes it public without opening a port on my home network. Python scripts handle deploys, and the site is backed up with the rest of the TrueNAS data.

Why Self-Host?

  • Control: I set the nginx config, security headers, caching, and compression myself
  • Instant Updates: I edit the files directly and changes go live without a git push
  • Visibility: I can read the nginx access and error logs
  • Monitoring: The site gets the same container alerts as every other service on the server
  • Cost: It runs on hardware I already had, with no hosting fees
  • Practice: I learned nginx, Docker networking, and Cloudflare along the way

Technical Architecture

Web Server nginx:alpine (Docker container)
Reverse Proxy Cloudflare Tunnel (cloudflared)
DNS Cloudflare DNS with proxy enabled
Storage ZFS dataset on TrueNAS mirror pool
Deployment Python deploy scripts on cron, Docker Compose
Monitoring Docker health check + Healthchecks.io

Migration Process

Phase 1: Infrastructure Setup


  • Docker Compose Stack: An nbnstack-web container on the nginx:alpine image
  • File Structure: Copied the GitHub Pages content to /mnt/SourPatchNAS/appdata/nbnstack.com/
  • nginx Configuration: gzip, browser caching, and security headers
  • Cloudflare Tunnel: A tunnel from TrueNAS to Cloudflare, so no port forwarding

Phase 2: DNS and SSL


  • DNS Migration: Pointed nbnstack.com at the Cloudflare Tunnel with proxied DNS
  • SSL/TLS: Cloudflare's Universal SSL handles the certificates
  • Canonical Host: nbnstack.com is the canonical domain
  • CNAME Records: Pointed the subdomains at the new setup

Phase 3: Automation and Monitoring


  • Deployment Script: deploy-website.sh validates metadata, refreshes cache-busting versions, and recreates the container
  • Scheduled Pipeline: update-site.py runs every twelve hours: refreshes Trakt and Plex data, versions assets by file timestamp, updates sitemap dates, validates every page's metadata, and purges the Cloudflare cache
  • Health Monitoring: nginx answers a Docker health check every 30 seconds. If the container stops or turns unhealthy, a server job emails me and Healthchecks.io sends the alert to my phone

Related Projects