Caddy inside Docker with Redirects

Caddy is a web server that boasts with easy configuration and great defaults, such as automatic HTTPS support by default. While I didn't need https, I was curious about how configuring some redirects and static file serving would go. You can find out more about caddy at caddyserver.com.

If you want to get the full source for this repository, the link is at the bottom.

As so often I needed redirects, as I was moving one of my websites to become a static site. Inside my docker container I would have a rendered directory structure that has the paths of the html files and of course some assets.

While migrating away from a CMS, I knew that I would break some of the links on the website. Blog posts like /peaches would become /blog/peaches, so redirects were important to me, optimally in an external file that I can load into the main config.

Caddy in the Dockerfile

The simplest way in which you can use caddy in a dockerfile to serve static assets might be this:

FROM alpine
EXPOSE 5000
RUN apk add --no-cache caddy
RUN mkdir -p /var/www/html
COPY pages/ /var/www/html/
WORKDIR /var/www/html
CMD ["caddy", "file-server", "--listen", ":5000"]

Given we have some html files in the pages directory like so:

pages
├── blog
│   ├── eggplant
│   │   └── index.html
│   ├── ice-cream
│   │   └── index.html
│   └── peaches
│       └── index.html
└── index.html

we can serve the files by building the Dockerfile and running it:

docker build . -t caddy_test
docker run -i -p 127.0.0.1:5000:5000 -t caddy_test

Now visiting localhost:5000 we can see our HTML files.

For me it looks like this with some very minimal example HTML:

ladybug browser showing example page

While this will serve our files, we're missing out on the redirects we need to apply.

Load your Caddyfile

The common config file for a caddy server is the Caddyfile, so let's create that one:

# file: caddy-config/Caddyfile
# global directive
{
  # turn off https, we're in a trusted environment
  auto_https off
}

:5000 {
  # import redirects
  import /var/app/caddy-config/redirects

  # set the root for our served files
  root * /var/www/html

  # Enable the static file server.
  file_server {
    precompressed zstd br gzip
  }
}

This one will also listen on port 5000 and through the file_server directive serve the files with some compression enabled.

We can place the file in a sub-directory like caddy-config if we don't want to clutter the root of our project.

Now to add redirects we could insert a few lines above the root directive, like so:

redir /ice-cream /blog/ice-cream permanent

Loading an External Redirects File

For the redirects, we're creating a separate file, which I chose to just call redirects without any specific file ending.

# redirect to front page
redir /tag/* / permanent
redir /author/* / permanent

# old post structure from /[name] to /blog/[name]

# exact match
redir /ice-cream /blog/ice-cream permanent

# match path and path with trailing slash
redir /peaches /blog/peaches permanent
redir /peaches/ /blog/peaches permanent

# match prefix, every route starting with /eggplant will be redirected
redir /eggplant* /blog/eggplant permanent

Please note that depending on how you want your redirects to work you might either want to match explicit paths, or use some of the wildcards you can pick up in the caddy matcher documentation.

To check if everything works to our satisfaction, we can run docker build and docker run again:

docker build . -t caddy_test
docker run -i -p 127.0.0.1:5000:5000 -t caddy_test

We should see output similar to this:

# [...] redacted
Successfully tagged caddy_test:latest
2024/06/16 11:54:18.241 INFO    using provided configuration    {"config_file": "/var/app/caddy-config/Caddyfile", "config_adapter": "caddyfile"}
2024/06/16 11:54:18.241 WARN    Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies    {"adapter": "caddyfile", "file": "/var/app/caddy-config/Caddyfile", "line": 1}
2024/06/16 11:54:18.242 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2024/06/16 11:54:18.242 WARN    http.auto_https automatic HTTPS is completely disabled for server       {"server_name": "srv0"}
2024/06/16 11:54:18.242 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc0001f6200"}
2024/06/16 11:54:18.242 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/06/16 11:54:18.242 WARN    tls     unable to get instance ID; storage clean stamps will be incomplete      {"error": "open /root/.local/share/caddy/instance.uuid: no such file or directory"}
2024/06/16 11:54:18.242 INFO    autosaved config (load with --resume flag)      {"file": "/root/.config/caddy/autosave.json"}
2024/06/16 11:54:18.242 INFO    serving initial configuration
2024/06/16 11:54:18.243 INFO    tls     cleaning storage unit   {"storage": "FileStorage:/root/.local/share/caddy"}
2024/06/16 11:54:18.243 INFO    tls     finished cleaning storage units

Now when visiting the example links we can see that we're being redirected 🙌!

firefox showing redirect

Summary

This was a very short introduction to redirects in caddy and how to load a config file with imported redirects from an external file. Thank you for reading! Let me know what you're using caddy for 👀!

Full source for this example can be found here: github.com/JonathanMH/stream-kitchensink/tree/master/caddy-docker

Tagged with: #caddy #docker

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!