Task Requirements
- Ensure you have a static IP address on the internet via your ISP (let’s try not to go for A&A for now).
- Install zola on your server or local machine.
- Generate a basic page with Zola.
- Figure out where to store the output from Zola on your Linux server.
- Use something like Caddy, Simple HTTP Server, or Basic HTTP Server to serve the webpage.
What I Actually Did (High Level)
Goal for this task: take a Raspberry Pi on my home internet and get a basic Zola site live on a real domain, using one of the suggested HTTP servers.
End state:
cainappleby.net→ home public IP → router → Raspberry Pi → Caddy → Zola-generated static files
I was told to use Zola specifically for this task. I picked Caddy because I like that it handles HTTPS and certificates automatically, and the config is readable enough for instant deployment you can copy paste their documentation with obvious skim reading changes.
For now I’m on a dynamic IP (no static IP from my current ISP). I’ve accepted that for Task 1 and I’ll solve it later with a dynamic DNS setup.
Environment and Constraints
- Hardware: Raspberry Pi running Ubuntu Server.
- Network: Home broadband, dynamic IP, behind a consumer router.
- Task constraints:
- Must use Zola.
- Must serve the site over HTTP(S) using one of the suggested servers.
- Real constraint:
- Static IP not available from normal ISPs I use → proceed with dynamic IP for now.
Installing Zola
Attempt 1 – Compile from Source (Pi said “nope”)
First approach was “do it properly” via Rust and Cargo, following the docs.
Installed Rust and Cargo:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shsource $HOME/.cargo/env
Then tried to install Zola:
cargo install --locked --git https://github.com/getzola/zola
This ran for a while and then died with an out-of-memory error at around 392/500 crates compiling.
Takeaway: compiling a big Rust project on a Raspberry Pi is a bit ambitious. Technically possible if I start doing swap tricks etc., but not worth it for this task.
Attempt 2 – Use the Official Release Binary
Plan B: grab the prebuilt release instead of compiling.
-
Went to the releases: https://github.com/getzola/zola/releases
-
Downloaded the ARM64 build:
wget https://github.com/getzola/zola/releases/download/v0.21.0/zola-v0.21.0-aarch64-unknown-linux-gnu.tar.gz
-
Extracted it and moved the
zolabinary into a standard place for manually installed tools:sudo mv zola /usr/local/bin/
After that, zola --version worked fine, and I could move on to actually building a site instead of fighting the compiler.
Zola Site Setup
With Zola installed, I set up the actual site:
-
Initialised a new Zola site in my home directory:
- Site name:
MainSite - Base URL:
cainappleby.net
- Site name:
-
Used the default templates and starter content from Zola to get something on screen.
-
Ran:
zola build
This produced the first working static site output in Zola’s default public folder. At this point it was a local static site only; no internet routing yet.
Firewall and Router Setup
I didn’t want to just fling the Pi on the internet without thinking about it a bit.
UFW (Firewall) Configuration
Allowed basic HTTP and HTTPS traffic into the Pi:
sudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw reload
So, from the Pi’s perspective, ports 80 and 443 are now open for inbound traffic.
Port Forwarding on the Router
On the home router I set:
- Port 80 → Raspberry Pi (HTTP)
- Port 443 → Raspberry Pi (HTTPS)
The actual local IP is redacted in this write-up; in my own notes I’ve replaced it with a placeholder so I’m not dumping my LAN details on the internet.
At this stage, if DNS has been set up correctly, any traffic to my public IP on 80/443 will get forwarded to the Pi.
Domain Registration
I didn’t want to test everything using raw IPs, so I registered a real domain:
- Bought:
cainappleby.net
This is the main entry point to the site for this task and the follow-on tasks.
Choosing and Installing Caddy
From the task requirements, I could pick between Caddy, Simple HTTP Server, or Basic HTTP Server (or something similar). I went with Caddy.
Reasoning in plain terms:
- Config is simple enough that I can read it in one go.
- It’s designed to serve static files nicely.
- It handles HTTPS and certificates automatically, which I wanted to lean on rather than hand-roll.
I followed the official guide:
- https://caddyserver.com/docs/getting-started
Once Caddy was installed and running, I pointed it at a directory that would later contain the Zola build output.
Wiring Zola Output into Caddy
By default, Zola builds into a public directory inside the project. That works fine, but I wanted Caddy to serve from a more standard location, and I didn’t want to manually copy files over every time.
In config.toml for the Zola site, I added:
output_dir = "/var/www/mainsite"
What this does:
- Tells Zola to build directly into
/var/www/mainsite. - Lets Caddy use
/var/www/mainsiteas its web root.
After changing the config, I ran:
zola build
and confirmed that:
- The site files appeared in
/var/www/mainsite. - Caddy was serving from there.
So the build step is now simply:
- Run
zola build. - Caddy serves whatever is in
/var/www/mainsite.
No extra copy step.
DNS Setup
With Caddy serving the site locally and the router forwarding ports correctly, the last piece was DNS.
Based on Caddy’s own guidance, I added A records so the domain resolves to my home IP:
A—@→ public IPA—www→ public IP
Quick definitions, mainly for future-me:
- A Record: maps a hostname to an IPv4 address (e.g.
cainappleby.net→X.X.X.X). @: shorthand for the root domain, in this casecainappleby.net.www: subdomainwww.cainappleby.net, also mapped to the same public IP.
Because my IP is dynamic, this setup will break if the IP changes. For Task 1, I’m accepting that limitation. Task 2.2 is where I’ll sort dynamic DNS properly from the Pi using the provider’s API.
Routing Summary
Inbound Path
From the outside world into the Pi:
- Client browser asks for
cainappleby.netorwww.cainappleby.net. - DNS resolver looks up the A record and gets my home public IP.
- Traffic hits the home router on port 80 or 443.
- Router forwards that traffic to the Raspberry Pi.
- Caddy on the Pi receives the request.
- Caddy serves the static files generated by Zola from
/var/www/mainsite.
Outbound Path
Response heading back out:
- Caddy sends a response back to the client.
- Response leaves the Pi, goes via the router.
- Router sends it back over the internet to the client’s IP.
Nothing wild here, but writing it out makes the path very clear.
Key Learnings (From Doing It, Not Theoretically)
-
Compiling big Rust projects on a Raspberry Pi is not fun
Cargo got as far as ~392/500 crates and then fell over due to memory. For this hardware and this use case, using the official binary is just the saner option. -
Zola’s
output_diris a small config that changes the whole flow
Pointingoutput_dirdirectly at the directory Caddy serves from means “build then done”. No extra deployment step. It’s a nice little quality-of-life thing. -
DNS feels simple until you mix root domain vs
wwwvs records
Getting really clear on:@= root domainwww= subdomain- A record = name → IPv4
makes debugging a lot less guessy.
-
Home-hosting is basically about joining a few simple pieces together
DNS → router → port forward → firewall → HTTP server → files.
The individual steps aren’t that scary once you see them as one chain.
Next Steps (Not Required for Task 1, But Lined Up)
These are outside the literal Task 1 requirements, but they’re the obvious follow-ons I’m planning:
-
2.1 – Improve the look of the site
Use Codex/AI to help tidy up the templates, make the blog more readable, and make sure it doesn’t look like a default scaffold forever. -
2.2 – Handle dynamic IP properly
From the Pi, use the DNS provider’s API to update A records when my public IP changes (simple script, compare stored IP vs current, update if changed). -
2.3 – Put the site under git
Track changes to templates and content with git so I can see what I did, when I did it, and roll back without panicking.
Those will each get their own write-up, but they all sit on top of the work in this task.