Site configs and nerding out
This site is powered by Caddy, which is an amazing web server. Secure by default, lightweight, high performance - all the things that other servers like Apache are not.
And right now I’m feeling very proud of myself because I did the reasonably obvious thing of splitting out the config file from a single monolith to break out “snippets” and “sites”.
- Snippets: small bits of config that I reuse over and over
- Sites: individual files for individual virtual servers
My main config file is now just a couple of lines:
import snippets/*.caddy
import sites/*.caddy
Snippets
Some bits of config are repeated in most or all sites. Rather than repeat it, I use a snippet to break it into a reusable block, like this:
(logging) {
log {
format json {
time_format iso8601
}
output file /var/log/caddy/{args[0]}.log
}
}
This creates a snippet called logging, which I can then use in a site definition in a single line, rather than duplicating everything. The {args[0]} part means I can pass the filename I want as a parameter.
Sites
Each virtual site is now configured by simply adding a new file into the sites directory. For example, stocksfamily.org.caddy. The filename doesn’t matter, but obviously it’s easier to manage if you use sensible names!
A site config looks something like this:
stocksfamily.org www.stocksfamily.org {
import logging www.stocksfamily.org
import robots
import clacks
root * /home/caddy/sites/stocksfamily.org
file_server
encode gzip zstd
}
Note the three import lines:
logging: write a log file for activity on this server to /var/log/caddy/www.stocksfamily.org.log (see the log snippet above).robots: Responds to requests for/robots.txtwithout having to actually create the file. I have the same requirements on most sites, so this saves a bunch of duplicate boilerplate.clacks: Adds anX-Clacks-Overheadheader.
So, this avoids repeating a lot of configuration. But I didn’t stop there…
I’ve also put all of those files under version control, so I can get history and rollback. But even geekier, I’ve added a devops pipeline, so that I can auto-deploy new config files directly from the source repository.
Where that gets a little trickier is making those changes live: Caddy is pretty good in this regard, the caddy reload command will perform a zero downtime refresh of the config. The wrinkle here is that command needs to be issued on the actual host. My Forgejo server and my web server are different hosts, and the continuous integration tasks run inside a Docker container. What to do?
Another nice thing about Caddy is that it ships with an admin API by default. I did have to modify the config to bind to a private network address (by default the API binds to localhost), but now the CI runner is able to:
- Copy the config files to the web server (keep a local copy in case of server restarts, etc, so it’s always in sync)
- Generate a new JSON version of the config
- Upload that new config via the admin API
Bingo, instant reload with the updated config, without actually touching the server at all!