Post

May 14, 2020
Easy updates for your Hugo blog using Caddy

Previously I wrote about my initial plan for creating this blog using Hugo. This is a brief post on how to make it super easy to deploy updates to your blog. In fact, as easy as a git commit! All it takes is pushing your latest changes for your blog to your git repository, and it shows up automatically on your live site. Please note, this post assumes familiarity with Docker.

I’m currently running Hugo on my Raspberry Pi, using traefik (which I do not recommend – use Caddy instead if you can) as a load balancer and reverse proxy, along with docker-compose files for easily starting and stopping my services. For this setup, it’s necessary to use Caddy 1 (in my case sitting behind traefik), since we make use of the http.git directive that doesn’t (as of this moment) have a built-in analog in Caddy 2. I’ll look to move to Caddy 2 at some point in the future.

To run Caddy on my RPI, I need an ARM build of Caddy. There’s one available via the elswork/arm-caddy image. Once Caddy is configured correctly, it will regularly check the git repository for updates, and, upon finding them, will run Hugo within the container to build the newest version. We aren’t pushing a pre-built version of the blog to our repo, but rather the raw files through which our Docker container can subsequently build the latest version for us. Here is an example Dockerfile that uses the ARM Caddy image, and downloads a specified version of Hugo to use:

FROM elswork/arm-caddy:1.0.0

ARG hugo_version=0.64.0

RUN apk add --no-cache openssh-client git tar curl

RUN curl --silent --show-error --fail --location \
  --header "Accept: application/tar+gzip, application/x-gzip, application/octet-stream" -o - \
  "https://github.com/spf13/hugo/releases/download/v${hugo_version}/hugo_${hugo_version}_Linux-64bit.tar.gz" \
  | tar --no-same-owner -C /tmp -xz \
  && mv /tmp/hugo /usr/bin/hugo \
  && chmod 0755 /usr/bin/hugo \
  && git config --global fetch.recurseSubmodules true \
  && mkdir -p /www/public

This should install everything you need to run Caddy in a docker container with git support to pull our latest changes, and Hugo to build the site. You can create this as a public project on Github or your preferred place, and then configure Dockerhub to create builds for you. That image can then be used in a docker-compose.yml file to start your service. E.g.:

version: "3.3"
networks:
  web:
    external: true
services:
  caddy:
    image: "saward/caddy-hugo-arm:1.0.0-0.64.0"
    container_name: "caddy"
    restart: always
    networks:
      - web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "./Caddyfile:/etc/Caddyfile"
      - "./certificates:/root/.caddy"
      - "/home/pi/.ssh:/home/pi/.ssh"
      - "/home/pi/caddylogs:/caddylogs"

This simply starts a docker container using our new image (built via Dockerhub). Some notes on the volumes:

  • The Caddyfile is stored on our filesystem, and we mount it from the host system.
  • We store certificates from Let’s Encrypt to avoid spamming their service whenever we restart.
  • SSH key is used so that if your repository is private, the git command can read the contents. I recommend using a read-only deploy key to minimise the damage if your server is ever compromised.
  • Logs are taken and mounted externally for analytics (a subject for another blog post).

Finally, we need a Caddy file (again, this is v1 of Caddy), where the magic happens. Using the http.git directive, we can instruct Caddy to clone our blog repository when it starts, and to check for updates every 5 minutes. Whenever there is a change, it will run the Hugo command to build our final version. The version of the site built within the container is what Caddy serves whenever someone hits the server at the manse.cloud domain!

manse.cloud {
        log / /caddylogs/manse.cloud/access.log {combined}
        root /home/pi/www
        git {
                repo     git@github.com:saward/mansecloud
                path     /home/mansecloud
                branch   master
                clone_args --recurse-submodules -j8
                key      /home/yourhome/.ssh/id_rsa
                interval 300
                then     hugo -s /home/mansecloud/blog --destination /home/pi/www
        }
}

And that’s it. Every time you have a new post ready (not a draft), and you push, you should see it show up live within 5 minutes. Easy.