Caddy is a super webserver that has many useful features. Caddy can enable very very powerful scenarios and many of them are documented in this Wiki. As these scenarios become more elaborate (some might say complex!) writing a caddy config file starts to feel more like programming than basic configuration.
When that starts to happen I find myself reaching out for variables to enable multiple scenarios in a single configuration file by manipulating those variables.
For those situations caddy has a few types of variables to consider. What I hope to do here is illuminate how to use these variable types in the Caddyfile.
I will use examples to illustrate the concepts. I use the respond
directive to verify how caddy
works.
All the caddy example configurations are in a file named Caddyfile
.
Open a command window and you can run caddy with caddy run
.
In another command window you will be able to execute the caddy reload
and curl
commands.
You can read about placeholders in the Conventions#placeholders section of the docs and more infortion in the Concepts#placeholders section of the docs.
Note: Placeholders are generally usable anywhere there is a text field you would like to substitute. But... not every field can substitute placeholders today. The team has worked hard enabling placeholders in as many places as they thought they would be super useful, but not all fields. If you run into a field that you think should be enabled for placeholders simply add an issue to caddy.
OK, let's see them in action!
Let's just respond to a query with a string that has substitutions.
# My Caddyfile...
:2022 {
respond "{time.now}:{system.os}:{system.arch}"
}
Remember to caddy run
in it's own command window 😸.
Now that we have changed the Caddyfile we can reload it with the caddy reload
command and execute the curl command.
$ caddy reload && curl localhost:2022
Expected output (something like):
2022-03-20 12:26:34.55429 -0700 PDT m=+59.288837501:linux:amd64
Great, I can see the time, my OS & my architecture separated by ":".
This just showed how you can reference placeholders
within a Caddyfile.
There are a couple of ways to set your own custom placeholders. Lets start with a map
.
# My Caddyfile...
:2022 {
map 1 {my_customvar} {
default "this_is_my_custom_var"
}
respond "{my_customvar}"
}
outputs: "this_is_my_custom_var
"
Great we have set our own custom variable.
Snippets can process arguments that are passed to the import
directive so this is a form of setting variables also.
# My Caddyfile...
# declare mycustomargs snippet
(mycustomargs) {
respond "arg0: {args.0} arg1: {args.1}"
}
:2022 {
import mycustomargs my_argument1 my_argument2
}
outputs: "arg0: my_argument1 arg1: my_argument2
"
Slick! The arguments can only be referenced inside the Snippet but this turns out great for some customizations.
OK, we can set custom placeholders and we can reference them. But hold on, they cannot be referenced everywhere! Lets try to reference a placeholder within a map directive.
# My Caddyfile...
:2022, :2023 {
map 1 {my_customvar} {
default "custom1"
}
map {port} {mynewvar} {
2022 "custom2022"
2023 {my_customvar}
}
respond "{my_customvar} {mynewvar}"
}
I am trying to create a new custom variable mynewvar
in the 2nd map
directive. For port 2022 I set it to a string. For port 2023 I set it to the reference of my other custom variable my_customvar
.
output from curl localhost:2022
: custom1 custom2022
so far so good, but...
output from curl localhost:2023
: custom1 {my_customvar}
Note: Directive ordering could be an issue here because the order of the 2 map directives is not deterministic. In this case it works in file order. But as you start to work on your Caddyfile it is important to know that the order of directives is not deterministic for multiple instances of the same directive.
To be more explicit we could have ordered the
map
directives in aroute
directive.
hang on, the custom variable my_customvar
is not evaluated in the 2nd map
configuration. It just set mynewvar
to "{my_customvar}
".
In Version v2.4.6 This is one of those places where placeholder substitution is not supported (yet). As part of writing this article I pointed out that substitution is not happening here and the Caddy team added that capability for later releases. (The team responded very quickly to my questions and suggestions which is a big reason I love working with Caddy).
But when doing this work, I had the thought it would be great if Caddy had a simple substitution method prior to parsing the Caddyfile. So I would not have to completely depend on placeholders.
How could I do that?
Caddy Environment variables are a little different in that they only work in a Caddfyfile (not a JSON
config file) and they perform the substitution before the file is parsed by caddy to create the webserver.
# My Caddyfile...
:2022, :2023 {
map {port} {mynewvar} {
2022 "custom2022"
2023 {$MYENVVAR}
}
respond "{$MYENVVAR} {mynewvar}"
}
$ MYENVVAR=custom1 caddy reload && curl localhost:2023
custom1 custom1
$ curl localhost:2022
custom1 custom2022
When I hit port 2023, "{$MYENVVAR}
" is substituted with the value of your environment variable MYENVVAR
and it even works on the parameters of the map
directive. So now when I hit localhost:2023
map
will set {mynewvar}
equal to the subtitution for {$MYENVVAR}
. Pretty nice, now I could paramaterize the parameters in a map
, yay!
BTW! Setting the environment variable with a reload works. That is what I call a "surprise & delight", it just kind of works like you hope it might. (I was not sure it would work... thinking I might have to restart the first command window.)
We can set a default value for any environment variable substitution, but it is not a global substitution for all variable references.
# My Caddyfile...
:2022, :2023 {
map {port} {mynewvar} {
2022 "custom2022"
2023 {$MYENVVAR:defaultValue}
}
respond "{$MYENVVAR} {mynewvar}"
}
$ MYENVVAR=custom1 caddy reload && curl localhost:2023
custom1 custom1
$ curl localhost:2022
custom1 custom2022
Still works the same, but what if I do not set the environment variable?
$ caddy reload && curl localhost:2023
defaultValue
$ curl localhost:2022
custom2022
This makes sense... MYENVVAR
is not a defined environment variable. The value for {$MYENVVAR}
is substituted with the value "defaultvalue
" specified in the map
parameters & {mynewvar}
gets set with that default substitution. But the {$MYENVVAR}
has no default in the response, so the empty string (value of the MYENVVAR
environment variable) is substituted there.
That's a wrap for now!
- What about variables in Caddy
JSON
configuration files? - How does an
expression
directive interact with variables? - How is a Named Matcher like a variable?
- What is this
vars
directive, how does it relate to variables?
"tip" refers to the latest commit in the repo, you can run
xcaddy build master
to build from the latest commit on the master branch (which is what Matt called "tip") https://caddyserver.com/docs/build#xcaddyGeneral comment,
Caddyserver
->Caddy
; the program is called "Caddy", and it happens to be a server (and the domain has server in it becausecaddy.com
was taken). For example (among many):Also "the Caddyfile" is the better way to say it; you're never working with many of them, just the one file. And being consistent about it and not pluralizing reinforces that it matches the name of the file on disk:
When giving command examples, use
$
instead which is a better indicator that it should be run in a terminal:FWIW I'm not a big fan of recommending
caddy start
because 99% of the time users should be running Caddy as a service, and if they installed it with theapt
repo or whatever, runningcaddy start
will conflict with the instance running as a service. Also, you may lose out on Caddy's log output, which is usually very important for debugging issues. There's just less "asterisks" involved when just saying "usecaddy run
", less ways for the user to shoot themselves in the foot. I also don't think it's relevant to this wiki article to mention that. It should be the job of the official docs, or other guides, to cover how to actually run Caddy.I think it's worth mentioning that you might run into trouble if you use a variable name that overlaps with any of the Caddyfile placeholder shortcuts as per https://caddyserver.com/docs/caddyfile/concepts#placeholders because the Caddyfile adapter will rewrite
{port}
to{http.request.port}
for example, before outputting the JSON, whereas you might expect to be able to use a variable named{port}
(and expect it to remain as{port}
in the JSON output), but you can't do that.I don't think it does much to number your examples like that, I think you can just make a
## Placeholder examples
section and then put those within, basically.Another thing to note, the sorting order of directives of the same type is not guaranteed, it may not be sorted according to the order you wrote it in your config. If you need to make sure one runs before the other, you'd need to wrap it in a
route
. But keep in mind thatroute
itself has its own directive order, and it's much lower thanmap
's by default, so it might cause your maps to run too late. I'd just caution against trying to make onemap
chain into another (or onevars
handler into another)https://caddyserver.com/docs/caddyfile/directives#directive-order
I think this is misleading -- firstly, I'd recommend to always
UPPERCASE
environment variables that you're talking about, to avoid confusion with other kinds of user-definedvars
/map
; Also,env.MY_ENVVAR
has no meaning on its own, that's not really a thing, because the placholder is{env.MY_ENVVAR}
(with the braces), but also that is only replaced at runtime which is a separate concept altogether. And quoting"my_envvar"
is also misleading because you'd never quote it like that in your Caddyfile, it would be written as{$MY_ENVVAR}
.