TLDR
- Tried to follow the spec of “static IP + Zola + simple HTTP server”, but ended up running on a dynamic IP for now.
- Installed Zola on a Raspberry Pi by abandoning a failed Cargo build and using the official ARM64 release binary instead.
- Initialised a Zola site (
MainSite), wired itsoutput_dirto/var/www/mainsite, and used Caddy to serve it. - Opened the firewall, set router port forwarding, and pointed DNS A records for
cainappleby.netandwww.cainappleby.netat my home IP. - Used
scpand some lazyrm -rfto push content/templates from my Windows machine to the Pi as the site evolved.
Task Requirements
- Get a basic Zola-generated site running on a Linux box (Raspberry Pi) and reachable from the public internet.
- Ideally obtain a static IP from the ISP, but in practice run on a dynamic IP until that’s solved.
- Install Zola on the Pi, generate a basic page, and decide where on the filesystem to store the build output.
- Use something like Caddy, Simple HTTP Server, or Basic HTTP Server to serve the site over HTTP(S).
- Understand and configure the basic chain: Zola (static files) → HTTP server → router (port forwarding) → WAN.
Goal
Take a bare Raspberry Pi on my home broadband and turn it into a publicly reachable static site host for cainappleby.net, using Zola for site generation and Caddy for serving, while learning the basic plumbing from DNS through router/firewall to the Pi.
Implementation Steps
-
I started with a Raspberry Pi running Linux (Ubuntu Server) on my home broadband, noted that I couldn’t get a static IP from my ISP, and accepted I’d be rebuilding or re-pointing things whenever the public IP changed.
-
To install Zola “properly”, I first installed Rust and Cargo as recommended, then tried to build Zola from source:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" cargo install --locked --git https://github.com/getzola/zolaThe compile got to roughly 392 of ~500 crates and then died with an out-of-memory error, so I gave up on compiling on the Pi.
-
As a Plan B, I downloaded the official ARM64 Zola release from GitHub, extracted it, moved the
zolabinary into/usr/local/bin/, and checked it was on the path withzola --versionso I could move on to actual site work. -
With
zolaavailable, I initialised a new site in my (empty) home directory usingzola init, gave it the nameMainSite, and set the URL to my current dynamic public IP when Zola asked “What is the URL of your site?”, accepting that I’d have to fix this later when the domain and DNS were sorted. -
Zola created a basic repository-style structure under
~/MainSite, includingconfig.toml,content/(withblogandwikisections and their_index.mdand example posts),sass/,static/,templates/(withbase.html,index.html,blog.html,blog-page.html,wiki.html,wiki-page.html), andthemes/, giving me a clear place to put content and templates. -
I added or tweaked HTML templates and any extra markup I needed (including sub-templates), knowing that Zola’s job here is essentially: “markup + config → static website”, and then ran the initial:
cd ~/MainSite sudo zola buildwhich generated the static site into the default
publicdirectory. -
I installed Caddy on the Pi by following the official documentation, then set up a basic
Caddyfiledescribing what to serve and from where, with the intention that Caddy would serve static files from a directory that would eventually be the Zola build output. -
To avoid copying files around manually, I edited
config.tomlin~/MainSiteand added:output_dir = "/var/www/mainsite"then re-ran
sudo zola buildso Zola wrote straight into/var/www/mainsite, which I configured as Caddy’s document root. -
I installed and configured UFW on the Pi to allow inbound HTTP/HTTPS and then reloaded it:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reloadtying this to the mental model that port 80 inbound is HTTP and port 443 inbound is HTTPS.
-
On the home router, I set up port forwarding so external traffic hits the Pi: forwarding port 80 and port 443 from the WAN side to the Pi’s internal IP address (e.g.
192.168.1.92), effectively linking the public internet to Caddy on the LAN. -
I registered
cainappleby.netvia Mythic Beasts, then pointed DNS at Cloudflare and created A records so that both the root and thewwwsubdomain resolve to my public IP:A @→your_public_ip(root domaincainappleby.net)A www→your_public_ip(www.cainappleby.net) This enforced the “A record = name → IPv4 address” idea and made it clear that@andwwwboth needed to point at me.
-
Once DNS propagated, the request chain looked like: DNS at Cloudflare resolves
cainappleby.netto my IP → router receives traffic and forwards 80/443 to the Pi → Pi hands it to Caddy → Caddy serves from its cache //var/www/mainsiteand sends the response straight back to the client IP. -
As I refined the site, I used some lazy but effective commands to sync content and templates from my Windows machine to the Pi, first wiping the existing content/templates and then copying over the local versions:
# wipe and replace content rm -rf /home/santigold/MainSite/content/* scp -r "C:/Users/CainA/Desktop/Zola/content/"* santigold@192.168.1.92:/home/santigold/MainSite/content/ # wipe and replace templates rm -rf /home/santigold/MainSite/templates/* scp -r "C:/Users/CainA/Desktop/Zola/templates/"* santigold@192.168.1.92:/home/santigold/MainSite/templates/followed by
sudo zola buildand a Caddy reload when needed. -
For day-to-day operations with Caddy, I leaned on systemd to manage the service:
sudo systemctl start caddy sudo systemctl stop caddy sudo systemctl restart caddy # Caddyfile and config sudo systemctl reload caddy # config only sudo systemctl status caddywhich gave me a simple control surface for testing changes and checking everything was healthy.
Notes & Decisions
- The original requirement wanted a static IP from the ISP, but I couldn’t get one, so I explicitly chose to run on a dynamic IP and accept that the site will need reconfiguration or a dynamic DNS solution later.
- Zola here is treated as a classic static site builder: it takes structured content, templates, and config in a “repository” and outputs a static site that Caddy can serve without any application runtime.
- The mental models I kept repeating were:
Zola → Caddy → router (port forward) → WANfor outbound, andWAN → router (port forward) → Caddy → Zola repositoryfor inbound, just to keep the path straight in my head. - Answering Zola’s “What is the URL of your site” with the current dynamic IP worked as a bootstrapping move, even though the “right” answer long-term is the actual domain name once DNS is stable.
- Pointing Zola’s
output_dirdirectly at/var/www/mainsite(Caddy’s root) simplified deployment into “runsudo zola buildand you’re done”, instead of having a separate copy/sync step each time. - The
rm -rf+scpdance for content and templates is deliberately lazy but effective for now; it trades safety for speed, which is fine at this stage but something I’d probably tighten up later. - Using Cloudflare for DNS in front of a Mythic Beasts-registered domain gave me a clear view of “Cloudflare knows my IP → router forwards → Pi → Caddy → back to user”, and reinforced the idea that DNS is just name → IP, nothing more magical.
Next Ideas / Follow-ups
- Implement dynamic DNS: script on the Pi that checks the current public IP and updates A records via the DNS provider’s API when it changes.
- Read the Mythic Beasts (or Cloudflare) DNS API documentation properly and prototype simple
curl/CLI calls from Linux to update records. - Wrap the dynamic DNS logic in a small, cron-able script or service so it runs automatically rather than as a manual task.
- Clean up the Zola templates and styling so the site stops looking like a default scaffold and actually feels like my blog.
- Put the Zola site and its templates/content under git so I have proper history, can see changes over time, and can roll back without panic.