I built this site with Hugo and immediately thought “alright, now where do I put it?” I could’ve just thrown it onto a cloud provider or GitHub Pages and called it a day, but where’s the fun in that?
How it all works
The setup is pretty simple:
- Hugo builds the site into plain HTML/CSS/JS files
- Caddy serves those files inside a Docker container
- Cloudflare Tunnel connects it all to the internet without exposing my home IP
- Everything runs on a Raspberry Pi
Hugo isn’t a server it’s just a build tool. You write markdown, run hugo --minify, and it produces public/ folder full of static files. Hugo’s job is done. Caddy is what actually hands those files to visitors.
Docker
Containers give me better isolation, if something gets compromised, it’s stuck in a container with a read-only filesystem and basically no Linux capabilities. The docker-compose.yml runs two containers on an internal network that only they can talk to each other on. Caddy has no ports exposed to the host or internet. it’s only reachable by cloudflared inside the Docker network.
The Cloudflare Tunnel
Instead of opening ports on my router I setup a Cloudflare tunnel. Cloudflared on my Pi reaches out to Cloudflare’s network, and Cloudflare routes visitors through that connection.
Zero open ports on my Pi, my home IP is completely hidden, and I get Cloudflare’s DDoS protection and edge caching for free.
Security Hardening
Since this is a cybersecurity portfolio, it would look really bad if I got hacked.
On the host: SSH is key-only and only accessible from my local network. UFW blocks everything else, and Fail2Ban watches for brute force attempts.
In the containers: Read-only filesystems, all Linux capabilities dropped except the bare minimum, and no-new-privileges so nothing can escalate.
On the web: Caddy sets security headers like CSP, X-Frame-Options, and X-Content-Type-Options. Common attack paths return 404s, and Cloudflare’s Bot Fight Mode filters out automated junk.
Speaking of bots, within minutes of going live I was already getting visits from bots scanning for WordPress logins and exposed config files.