Skip to content
Daniel Krol edited this page Dec 28, 2024 · 36 revisions

Caddy is a web server written in Go. It is known for being simple to install, and has batteries included. It won't hurt to look over the documentiation but here I'm going to give us a "quick start" as it pertains to using it in Sandstorm apps instead of Nginx. I will say that while I did have frustrations in the process of figuring this out, it was less than with Nginx.

Batteries include very useful things like load balancing. That sounds like overkill but it's actually useful for us. They also include things like automatic HTTPS and Let's Encrypt, which are very cool but only get in our way for Sandstorm development, so we disable them.

Caddy is known for being "fast enough" but not quite as fast as Nginx. This is no problem for Sandstorm. Easy is what we're going for, even for developers.

Installation

You can install in setup.sh like Nginx:

apt install caddy
service caddy stop
systemctl disable caddy

If you want a more updated version, you can look at options here appropriate for build.sh or setup.sh.

Configuration

Caddyfile

Caddy offers a few options for configuration. For Sandstorm, we will use what's called a Caddyfile.

Here's a basic Caddyfile that works for Sandstorm:

# This is the "global options block". Certain options only belong here.
{
	# Caddy by default supports https (and even handles ACME certs). For our
	# purposes this is annoying at best.
	auto_https off

	# The admin API would let you edit this config over http (!) at localhost:2019.
	# Sandstorm presumably wouldn't allow outside requests to hit it, but why add the risk?
	admin off
}

# This is a "site block" which handles requests to port :8080 (See ":8080 questions" below)
:8080 {
	log {
		output stdout
		format json

		# If you want to log every request, remove this line or replace it with "level info"
		level error
	}

	# `handle_path` will trip the prefix. (if you don't want this, use `handle`)
	# Ex: /static/img/cat.png -> /img/cat.png
	handle_path /static/* {
		file_server {
			# Ex: /img/cat.png -> /opt/app/static/img/cat.png
			root /opt/app/static

			# Allow directory browsing (with a nice interface!)
			browse
		}
	}

	reverse_proxy :8081 {
		# See: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#load-balancing
		
		# If Caddy starts before our app does, we want it to stall any incoming
		# requests instead of returning an error. We can do this using Caddy's
		# built-in load balancer (this may be overkill but it's a very simple
		# solution).

		# Retry for up to 5 seconds if the app hasn't started yet.
		lb_try_duration 5s

		# By default lb_try_interval is .250. To match 5 seconds we want 20 retries.
		lb_retries 20
	}
}

Again, for your specific needs, you will likely want to consult the documentiation. I could be recalling my struggles with Nginx wrong, but I think Caddy was a lot easier to understand with path editing directives in particular: rewrite and uri

TODO: :8080 questions

A few things that I'm unsure of related to this (that I would like to sort out and remove from this doc)

  • It's not 100% clear to me why this clause applies to http rather than https, but that is what we want and it's less verbose than http://:8080. If it's something that tends to be be finicky, maybe we should just recommend http://8080 to avoid debugging problems.
  • If you put localhost:8080 instead of :8080, the server will receive requests from Sandstorm (i.e. I can get log entries), but the page does not show up in the browser. I guess :8080 means 0.0.0.0:8080, which I hope is safe. (It must be safe, it's Sandstorm, right?)

launcher.sh

Like with Nginx, we start the app on the port that Caddy expects, we run the app in the background, and we run Caddy in the foreground.

However, unlike with Nginx, we have no need to have a retry loop to wait for the app to start before starting Caddy. We already told Caddy's built-in load balancer to do this job for us!

For this example, we will place the Caddyfile in the .sandstorm directory, but you can put it anywhere.

#!/bin/bash
set -euo pipefail

# Set your app to work on port 8081. How it is done depends on your
# app of course, but in our example we'll pretend that it's an
# environmental variable.
export MY_APP_PORT=8081

# <Other app configs go here>

cd /opt/app
my-app&

# Caddy saves certs and admin-API generated configs in the home
# directory. We don't care about either of these things.
FAKE_CADDY_HOME=/tmp/fake-caddy-home
mkdir -p $FAKE_CADDY_HOME

# Don't export the fake HOME; only apply it to caddy.
# By default caddy looks for `Caddyfile` in the current directory, but
# we are currently in /opt/app so we pass it in explicitly.
HOME=$FAKE_CADDY_HOME caddy run --config .sandstorm/Caddyfile

exit 0

Future ideas

Vagrant-SPK

If we decide that it's useful, we can update vagrant-spk to use this instead of Nginx.

Perhaps we'll want the Caddyfile in the service-configs directory, but unlike Nginx I don't think we'll need a separate mime.types file.

Turning off "persist"

One global option we could add is turning off "persisting" configs.

persist_config off

This appears to be unavailable in Debian Bookworm, but if you use a newer version it might be worth considering.

This setting would stop Caddy from wanting to save the admin API config to disk (see above). Since we use the "fake home" trick anyway, this would amount to one less log entry for us. Still if we could also stop Caddy from wanting to save the cert, perhaps we could do away with the "fake home" hack. Something to consider.

Clone this wiki locally