Skip to main content
  1. Posts/

Automating Caddy on my DigitalOcean Droplet

·712 words·4 mins·
Nick Dumas
Table of Contents

Defining units of work

I’ve got a few different websites that I want to run: this blog, my portfolio, and my about page which acts as a hub for my other sites, my Bandcamp, and whatever else I end up wanting to show off.

To keep things maintainable and reproducible, I decided to stop attempting to create a monolithic all-in-one configuration file. This made it way harder to keep changes atomic; multiple iterations on my blog started impacting my prank websites, and it became harder and harder to return my sites to a working state.

Proof of concept

The first test case was my blog because I knew the Hugo build was fine, I just needed to get Caddy serving again.

A static site is pretty straightforward with Caddy: {
    encode gzip
    root * /var/www/

And telling Caddy to load it:

curl "http://localhost:2019/load" \
	-H "Content-Type: text/caddyfile" \

This all works perfectly, Caddy’s more than happy to load this but it does warn that the file hasn’t be formatted with caddy fmt:

[{"file":"Caddyfile","line":2,"message":"input is not formatted with 'caddy fmt'"}]

The loop

Here’s where things went sideways. Now that I have two unit files, I’m ready to work on the tooling that will dynamically load my config. For now, I’m just chunking it all out in bash. I’ve got no particular fondness for bash, but it’s always a bit of a matter of pride to see whether or not I can.

# load-caddyfile
#! /bin/bash

function loadConf() {
    curl localhost:2019/load \
        -X POST \
        -H "Content-Type: text/caddyfile" \
        --data-binary @"$1"

loadConf "$1"
# load-caddyfiles
#! /bin/bash

source load-caddyfile

# sudo caddy stop
# sudo caddy start
for f in "$1/*.caddy"; do
    echo -e "Loading $(basename $f)"
    loadConf "$f"

After implementing the loop my site started throwing a 525 while continued working perfectly. This was a real head scratcher, and I had to let the problem sit for a day before I came back to it.

After some boring troubleshooting legwork, I realized I misunderstood how the /load endpoint works. This endpoint completely replaces the current config with the provided payload. In order to do partial updates, I’d need to use the PATCH calls, and look who’s back?

can’t escape JSON

The PATCH API does let you do partial updates, but it requires your payloads be JSON which does make sense. Because my current set of requirements explicitly excludes any JSON ( for now ), I’m going to have to ditch my dreams of modular code.

Not all my code has gone to waste, though. Now that I know POSTing to to /load overwrites the whole configuration, I don’t need to worry about stopping/restarting the caddy process to get a clean slate. load-caddyfile will let me keep iterating as I make changes.


In addition to the static sites I’m running a few applications to make life a little easier. I’ll showcase my Gitea/Gitlab and Asciinema configs. At the moment, my setup for these are really janky, I’ve got a tmux session on my droplet where I’ve manually invoked docker-compse up. I’ll leave cleaning that up and making systemd units or something proper out of them for a future project.

Reverse proxying with Caddy is blessedly simple: {
    encode gzip
    reverse_proxy localhost:10083
} {
    encode gzip
    reverse_proxy localhost:3069

With that, my gitea/gitlab is up and running along with my Asciinema instance is as well:


Back to Square One

After finally making an honest attempt to learn how to work with Caddy 2 and its configurations and admin API, I want to take a swing at making a systemd unit file for Caddy to make this a proper setup.


Here’s what’s currently up and running:

I’ve had loads of toy projects over the years ( stay tuned for ) which may come back, but for now I’m hoping these are going to help me focus on creative, stimulating projects in the future.

The punchline is that I still haven’t really automated Caddy; good thing you can count on tmux


The final code can be found here. Nothing fancy, but troublesome enough that it’s worth remembering the problems I had.


Prometheus Primer: the first few pages of the documentation
·579 words·3 mins
I’m trying to teach myself Prometheus, so I’m writing about it.