How I built my website and blog

Posted on Apr 23, 2026
tl;dr:

I’ve been a Fastmail user for years. One of the quieter features they offer is WebDAV-based web hosting which is not something they shout about, but it is genuinely useful if you know how to use it. Point your files at a WebDAV endpoint and Fastmail serves them as a static website. Paired with Hugo and a GitHub Actions pipeline, it makes for a surprisingly clean personal blogging setup. No separate hosting bill or third-party CDN to faff with.

Here’s how I put it together.

The overall architecture

Three main pieces. A one-page HTML site which is a simple index.html I maintain by hand and commit to its own GitHub Repo. A Hugo blog, with posts written in Markdown, committed to another repo, and then built automatically into HTML. Both of these are controlled by a GitHub Actions workflow which builds the site using Hugo and pushing the output to Fastmail over WebDAV.

The result is that writing a new post means dropping a Markdown file into the repo, committing, and pushing. A few minutes later the built HTML appears on the site, without lifting another finger.

Basically VS Code is where I write my content.

The Hugo side

Hugo is a static site generator written in Go. Fast, no runtime dependencies, clean HTML output. To create a new post:

hugo new posts/my-new-post.md

This drops a new Markdown file into content/posts/ with front matter pre-populated:

---
title: "My New Post"
date: 2026-04-24
draft: false
---

Post content goes here...

The repo structure looks roughly like this:

my-blog/
├── archetypes/
├── content/
│   └── posts/
│       └── my-new-post.md
├── layouts/
├── static/
├── themes/
├── hugo.toml
└── .github/
    └── workflows/
        └── deploy.yml

Running hugo locally produces a public/ directory full of HTML, CSS, and assets. That’s exactly what gets pushed to Fastmail.

Setting up Fastmail WebDAV

Fastmail lets you host static files via WebDAV under a subdomain tied to your account. To configure it, log in and go to Settings → Files, note your WebDAV endpoint, then generate an App Password under Settings → Privacy & Security → App Passwords, scoped to Files/WebDAV. Store it somewhere safe as you’ll need it shortly.

The WebDAV root maps to a directory in your Fastmail file storage. Any HTML files placed there are served as a website, with index.html as the default document.

The GitHub Actions workflow

The workflow triggers on every push to main, builds the Hugo site, and uploads the output to Fastmail via WebDAV using curl.

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Set up Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: "latest"
          extended: true

      - name: Build site
        run: hugo --minify

      - name: Deploy to Fastmail via WebDAV
        env:
          WEBDAV_URL: ${{ secrets.WEBDAV_URL }}
          WEBDAV_USER: ${{ secrets.WEBDAV_USER }}
          WEBDAV_PASS: ${{ secrets.WEBDAV_PASS }}
        run: |
          find public/ -type f | while read file; do
            remote_path="${WEBDAV_URL}/${file#public/}"
            curl --silent --show-error \
              --user "${WEBDAV_USER}:${WEBDAV_PASS}" \
              --upload-file "${file}" \
              "${remote_path}"
          done

A few things worth noting. submodules: true is needed if your Hugo theme lives as a Git submodule and without it the build fails silently. extended: true covers themes that require Hugo Extended for SCSS support; it doesn’t hurt to always enable it. And --minify strips whitespace from the output, which is a free performance win. The deploy step walks every file in public/ and uploads it individually via curl, WebDAV’s PUT method maps cleanly onto this.

GitHub secrets

The workflow reads three secrets, which you’ll need to add under Settings → Secrets and variables → Actions:

SecretValue
WEBDAV_URLYour Fastmail WebDAV base URL, e.g. https://myfiles.fastmail.com/blog
WEBDAV_USERYour Fastmail email address
WEBDAV_PASSThe App Password you generated earlier

Creating directories on WebDAV

One gotcha: WebDAV won’t automatically create intermediate directories when you upload a file to a path that doesn’t exist yet. Hugo’s output has nested directories like posts/my-new-post/index.html, so those directories need to exist on the remote before uploading. A more robust deploy step handles this in two passes, first creating all directories, then uploading all files:

find public/ -type d | while read dir; do
  remote_dir="${WEBDAV_URL}/${dir#public/}"
  curl --silent \
    --user "${WEBDAV_USER}:${WEBDAV_PASS}" \
    --request MKCOL \
    "${remote_dir}" || true
done

find public/ -type f | while read file; do
  remote_path="${WEBDAV_URL}/${file#public/}"
  curl --silent --show-error \
    --user "${WEBDAV_USER}:${WEBDAV_PASS}" \
    --upload-file "${file}" \
    "${remote_path}"
done

The || true on the MKCOL call suppresses errors for directories that already exist, which keeps the workflow output clean.

The writing workflow in practice

Day-to-day, it looks like this:

hugo new posts/fastmail-webdav-hosting.md
$EDITOR content/posts/fastmail-webdav-hosting.md
hugo server -D
git add .
git commit -m "Add post: fastmail webdav hosting"
git push

Within a couple of minutes of pushing, the post is live. The Actions tab in GitHub shows the build and deploy steps as they run.

What works well

It’s cheap — I’m already paying for Fastmail for email, and the file hosting is included. No separate hosting cost. It’s simple — no database, no server-side code, no CMS to update, just files. If something goes wrong, the debugging starts with a text file. And Hugo is fast: even as the number of posts grows, build times stay well under a second locally, and the whole GitHub Actions workflow typically finishes in under two minutes.

Rough edges

It’s not perfect. The workflow re-uploads every file on every push, even unchanged ones which is fine for a small personal blog, but it would slow down with hundreds of posts and assets. There’s no cache invalidation either, so changes to existing files won’t be picked up immediately by visitors who’ve cached the old version. And WebDAV is an old protocol; Fastmail’s implementation is solid, but it’s worth knowing you’re swimming against the current a bit.

None of those are dealbreakers for a personal blog. But worth keeping in mind if the site ever grows.

Overall it’s a setup that scratches the itch of owning a small corner of the web without babysitting a VPS or paying for separate hosting. If you’re already a Fastmail subscriber, it’s worth experimenting with.