← Blog

Task 2 - Serve Website

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 its output_dir to /var/www/mainsite, and used Caddy to serve it.
  • Opened the firewall, set router port forwarding, and pointed DNS A records for cainappleby.net and www.cainappleby.net at my home IP.
  • Used scp and some lazy rm -rf to 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

  1. 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.

  2. 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/zola
    

    The 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.

  3. As a Plan B, I downloaded the official ARM64 Zola release from GitHub, extracted it, moved the zola binary into /usr/local/bin/, and checked it was on the path with zola --version so I could move on to actual site work.

  4. With zola available, I initialised a new site in my (empty) home directory using zola init, gave it the name MainSite, 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.

  5. Zola created a basic repository-style structure under ~/MainSite, including config.toml, content/ (with blog and wiki sections and their _index.md and example posts), sass/, static/, templates/ (with base.html, index.html, blog.html, blog-page.html, wiki.html, wiki-page.html), and themes/, giving me a clear place to put content and templates.

  6. 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 build
    

    which generated the static site into the default public directory.

  7. I installed Caddy on the Pi by following the official documentation, then set up a basic Caddyfile describing 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.

  8. To avoid copying files around manually, I edited config.toml in ~/MainSite and added:

    output_dir = "/var/www/mainsite"
    

    then re-ran sudo zola build so Zola wrote straight into /var/www/mainsite, which I configured as Caddy’s document root.

  9. 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 reload
    

    tying this to the mental model that port 80 inbound is HTTP and port 443 inbound is HTTPS.

  10. 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.

  11. I registered cainappleby.net via Mythic Beasts, then pointed DNS at Cloudflare and created A records so that both the root and the www subdomain resolve to my public IP:

    • A @your_public_ip (root domain cainappleby.net)
    • A wwwyour_public_ip (www.cainappleby.net) This enforced the “A record = name → IPv4 address” idea and made it clear that @ and www both needed to point at me.
  12. Once DNS propagated, the request chain looked like: DNS at Cloudflare resolves cainappleby.net to my IP → router receives traffic and forwards 80/443 to the Pi → Pi hands it to Caddy → Caddy serves from its cache / /var/www/mainsite and sends the response straight back to the client IP.

  13. 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 build and a Caddy reload when needed.

  14. 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 caddy
    

    which 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) → WAN for outbound, and WAN → router (port forward) → Caddy → Zola repository for 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_dir directly at /var/www/mainsite (Caddy’s root) simplified deployment into “run sudo zola build and you’re done”, instead of having a separate copy/sync step each time.
  • The rm -rf + scp dance 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.