- Published on
Caddy: Imports
- Authors
- Name
- Alex Lee
- @alexjoelee
Welcome back! We're going to examine one of Caddy's most powerful features: Imports
What are imports?
An import is a Caddy directive which includes a snippet or a file, replacing the directive with the contents. Sounds kind of complicated - but it's not. Let's say that this is my file structure:
Caddyfile
imports/
--- global
--- headers
--- routing
--- static
sites-enabled/
--- (Use symlinks - example: 'ln -s /etc/caddy/sites-available/landing /etc/caddy/sites-enabled/landing')
sites-available/
--- landing
--- application
--- landing-beta
--- newsletter
--- analytics
Note that all of the files you see here are just text files, so you could put on an arbitrary file extension if it helps (e.g. .cfg). Caddy does not care.
Order matters
Here's our Caddyfile - this is the one that we call with the Caddy application itself.
import imports/global
import imports/routing
import imports/headers
import imports/static
# Import all files in sites-enabled
import sites-enabled/*
Notice our 6 lines of configuration are ordered specifically: We want our global
file loaded first, then our routing
file, then headers
, then static
. The reason for this is because we may want to import a snippet from the headers
file in the static
file, for example. If the headers
file is imported after the static
file, then the configuration won't load.
In our global
file, we're going to create our global options block.
{
email certificates@company.org
cert_issuer zerossl thisismysupersecretapikey
log default {
format json
level info
output file /var/log/caddy/caddy.log
}
servers {
metrics
}
}
Looks great! Keeping our configuration set up in organized blocks like this makes editing so much easier. Let's start reusing imports now.
Creating reusable imports
So far, we've imported full files, but not snippets. You can define a snippet by creating a name and surrounding it in parentheses:
(snippet-name) {
# config goes here!
}
We can put this to use in tons of scenarios, but the first one we'll cover is defining your upstream once and importing that for multiple sites.
(proxyto-chicago) {
reverse_proxy {
to ord1.company.org ord2.company.org
lb_policy round_robin
lb_retries 3
fail_duration 30s
max_fails 3
unhealthy_latency 700ms
header_down X-Routed-Via ORD
}
}
These ~8 lines of configuration define a snippet that we can import on any site. All requests to the site will be load-balanced and proxied between two upstream servers, ord1.company.org
and ord2.company.org
, with some health check parameters and a custom header.
Let's import it on our landing page:
https://www.company.org,
https://company.org {
import proxyto-chicago
}
We'll also import it on our landing-beta page:
https://landing-beta.company.org {
import proxyto-chicago
}
We can use this same import on more than one site, as we can see above. Love this for us! But let's amp it up and introduce some arguments.
Using arguments to customize imports
Arguments are awesome - there's no other way to say it. Let's give you an example first, since this one can get a little complicated.
(loadbalancer) {
reverse_proxy {
to {args[1:]}
lb_policy round_robin
lb_retries 3
fail_duration 30s
max_fails 3
unhealthy_latency 700ms
header_down X-Routed-Via {args[0]}
}
}
(proxyto-chicago) {
import loadbalancer ord ord1.company.org ord2.company.org
}
In this configuration, we've created an import snippet called 'loadbalancer' that takes some arguments. On line 3 of the snippet, we see that we're directly referencing args by using {args[]}
. In our case, we want to reference all arguments, starting with position 1, so we use {args[1:]}
meaning 'starting with 1, give all arguments'. There are lots of options for selecting arguments in the range:
{args[1]} where n is the 0-based positional index of the parameter:
import name [arg0] [arg1] [arg2] [arg3] [arg4] [arg5] (... forever)
{args[:]} where all the arguments are inserted
{args[:5]} where all arguments before the 5th argument are inserted
{args[6:]} where all arguments, beginning with the 6th argument, are inserted
{args[n:m]} where the arguments in the range between n and m are inserted
Near the bottom of the snippet, we reference a single argument in position 0. {args[0]}
refers to the argument in position 0 - again, arguments are 0-based, not 1.
When we import it for use, we start with the import
directive, then call the import by name loadbalancer
. Next up is position 0 ord
, in our case, this is just a loadbalancer name we're adding to a header for troubleshooting purposes. In the following positions, we can place as many upstreams as we want, since we called the arguments as a range {args[1:]}
.
Continue Reading
Don't stop there! There are countless excellent examples using Imports and Snippets on Caddy's official documentation site.
Wrapping up
Imports are an incredible way to save time when writing large or complicated Caddyfile configurations. Creatively used, imports and snippets could be combined with other Caddy directives to create simple web applications - all within the Caddyfile!
Want to learn more about Caddy?
Visit their official website at caddyserver.com Read through the docs at caddyserver.com/docs If you use it, sponsor the project (we do!) caddyserver.com/sponsor
This post is one of a series of tutorials about Caddy Server. You can view all relevant posts here.