Caddyfile macros (or inline includes)

I feel like this has been discussed before, but I can’t seem to find anything in issues or on here. Maybe I’m bad at search.

Problem statement:

I have a bunch of directives that I want to use in pretty much every server block in a large caddyfile. This includes auth, ip-filters, jwt stuff, common logging and errors, and so forth.

The current way to do it is by using import common.conf in each block, which does work technically. But I really really like having everything in one file. What if I could define a named block and include it later? Wouldn’t that be pretty cool?

A proposed syntax:

macro common {
    errors stderr

mydomain.tld {
    root ./somefolder

A few notes:

  1. Syntax is macro name { ... }. Braces are mandatory.
  2. name can be anything, excepting valid directive names.
  3. I have here inclusion by simply using the name in a server block. I don’t feel a new keyword is required or anything like that. Conceptually, I think of it as defining new macro directives on the fly.
  4. Can work with simple token replacement, much like imports currently do. Just have the dispenser save the token stream between the braces and re-inject it as needed.


  • It’s awesome! Complicated shared behaviours in one file without copy/paste!
  • I think I could patch this into the current parser pretty easily with minimal impact.

Potentially negative:

  • Could be confusing to read caddyfiles on the internet. I feel this is mitigated by limiting the syntax, not allowing nested macros or arguments or anything like that.
  • Could cause conflicts with future added directives. Maybe a new keyword like use commonStuffto “include” a macro would eliminate that risk (but I like the naked way better)

Anyway, thought I’d bring it up. I plan to hack on it a bit, just to see how it ends up looking. Is there any hope you’d include a feature like that @mholt?

1 Like

How about single quotes around it as a literal block name instead of a site label:

'common' {
} {
    import common

This way the name can be anything, even multiple words as long as double quotes are used around it (although I am not sure I would recommend that) so it is a single token.

In fact we could upgrade the import directive so that it works inside directives too.

My first reaction is that that could be more confusing. The last thing I’d want is for a new user to see something like that, assume it is a real server block, and then start putting quotes around their server names, and getting nothing served because they inadvertently defined a bunch of macros only.

I like using macro [name] as a top-level directive a la import but with a brace block.

I don’t like having [name] be usable as its own directive. I think import [name] is better. Just have import first look through available inline includes for a match before looking for an actual file to import, it can pull double duty here. Especially if you can use it as a subdirective, which would make the macros even more versatile.

1 Like

What if it was in parentheses instead, as “kind of” a site block:

(common) {
} {
    import common

I want to avoid reserving names (other than import) as much as possible so that they can be used as hostnames. Special characters, though, can’t be, so we don’t have to make more exceptions and special cases.

Ahh, I see where you’re coming from.

Yeah, I could go for @[name] or something. Called later on via import @[name]. Swap for any appropriate prefix symbol.

Can’t we elevate a server block to a macro by using a macro directive in it?

mydomain.tld {
    root ./somefolder

This way you won’t squat an identifier that can be uses as a host (or zone name). "@mydomain.%%tld is a perfectly valid domain name

I still feel like macro foo { } is the “least surprise” way to define it, but I’m trying to appreciate the other ways.

@miekg, I think I see the intent of adding macro inside another server block, but it kinda feels like a landmine to me. When reading you don’t really know that this is a “special server block” until you read into it a ways, and even then you gotta know what the “macro” keyword does.

@matt and @Whitestrake suggest alternate syntax like (foo) {} or @[foo] { }, but again, it seems this syntax is not very clear what it means, or why these new odd symbols are showing up. It seems to go against the simplicity of the entire reason for having a “simpler” config format. And as miek pointed out, even those could have domain name conflicts if someone is determined to do something crazy.

I still feel having it explicitly declared as a macro is a good thing. Maybe the (foo) { } syntax could grow on me though.

You have convinced me that an explicit import keyword should be required though.

I think you’re right. But how do I tell the difference between a macro definition and a non-macro def?

Made a PR for my initial implementation: Went ahead and implemented both forms.

In short, if a server is exactly macro foo or (foo) it will be interpreted as a macro. Otherwise it is a regular server.

Ah wait, just thought of a fix to have this as a zone in CoreDNS, just prefix with dns://macro.

But this is a backward incompatible change for someone having macro foo in the (in my case) Corefile.

Yeah, it would be. I feel like that should be rare enough that we don’t need to worry about it. But hmm. The paren syntax seems even more unlikely to collide. Maybe that’s a good thing.

1 Like

FWIW: I don’t like the parens syntax

I am a bit unsure now. Is there a functionality like that or will there be one, in the future (via a plugin?)?
I’d like to see that feature and would go for the first suggestion from @matt

@d1rk, it’s not yet a feature, but there’s an open Pull Request here:

Looks like it’s pretty close to implementation.

Just I thought I had last night; how is this different from env variables in a Caddyfile? That syntax is {$ENV}, maybe change the sigil a bit to? # or something?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.