Variables in Caddyfile

Variables in the Caddyfile

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.

A note about examples

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.

Placeholders

You can read about placeholders in the Conventions#placeholders section of the docs and more information 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!

Simple reference

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 :smile_cat:.

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.

Setting a custom placeholder with map

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.

Using custom placeholders inside a snippet

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.

Custom placeholder limits

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 a route 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?

Environment Variables

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.

Using environment variables

# 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.)

Unintialized environment variables & defaults

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!

Other topics for possible later post(s)

  • 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?

7 Likes