Plugin Development Environment with Step Debugging

I am looking to start developing some plugins for Caddy, but am somewhat new to both Go and Caddy. What I find to be by far the most useful whenever I start learning a new language is to get my development environment set up with Step Debugging. This allows me to follow the flow of the application, inspect variables where things are breaking etc…

Can anyone provide some guidance on how to get step debugging set up for Caddy Plugin development, ideally for VS Code?

This previous topic has a small suggestion, but I’d appreciate if it could be elaborated upon a bit more (Debugging Modules). Or, if there’s a newer, better way to do it, some details would be great.


Follow the instructions in debugging · golang/vscode-go Wiki · GitHub, if you build with the command xcaddy build --with $(grep module go.mod | awk '{print $2}')=$PWD, then you can run that binary with the delve debugger in exec mode to get step debugging.

99% of the time though, I just use fmt.Printf("\n%v\n", var) spam all over the place in code and rebuild/rerun. I find it takes more time to set up breakpoints and step than just printing out what I need :sweat_smile:

1 Like


It took me a while to figure out what was going on, but after inspecting that line a bit and fiddling around, I’ve realized the following:

  • Perhaps as should have been obvious, you need to run that command (xcaddy build --with $(grep module go.mod | awk '{print $2}')=$PWD) from the root directory of your plugin
  • The (grep module go.mod | awk '{print $2}') part is just looking within the go.mod file for the first module line and then extracting the module name/plugin repo url (e.g.
  • The $PWD part is, obviously, the path of the present working directory in your terminal. I suppose you could even just replace =$PWD with =./

So, what its really doing is just dynamically populating this command, which is a way to build the caddy binary with a local version of a plugin package repo.

xcaddy build --with

This is already explained in the examples in the Custom Builds section of the xcaddy Readme

However, this alone didn’t seem to work for me. What I needed to do was set the XCADDY_DEBUG environment variable with export XCADDY_DEBUG=1.

@francislavoie is there a way to provide that variable as a CLI flag? If not, could it be added? That would result in much better DX than having to add an environment variable.

This config in launch.json also worked for me

    "name": "Debug Caddy (exec)",
    "type": "go",
    "request": "launch",
    "mode": "exec",
    "program": "${fileDirname}/caddy"

Change the program path to whatever you like - an absolute path to the caddy binary, or whatever else. What I’ve used there will use the path of whatever file is currently selected in the editor (e.g. the Caddyfile).

Then just launch it from the debugger tab.

An unexpected bonus of all of this is that you can also step debug Caddy itself. However, it uses the code that is stored in ~/go/pkg/mod/ If you want to step debug a locally cloned (and perhaps modified) version of Caddy, you can clone the caddy repo somewhere (perhaps in a sibling directory within a parent project directory) and then build caddy with this (as was already explained in the xcaddy readme:

xcaddy build --with $(grep module go.mod | awk '{print $2}')=$PWD --with

Finally, the instructions in the Xcaddy Readme’s For plugin Development section are useful. As it turns out, you don’t need to do any of the --with stuff if you just use xcaddy run or xcaddy list-modules from within the plugin’s root directory. If the XCADDY_DEBUG env variable is set in the terminal, then it’ll build it with debugging enabled (you can see -gcflags all=-N -l in the terminal output, which indicates debugging support).

However, I can’t seem to leverage this to only run xcaddy build (without the --with grep stuff) from the plugin directory - it builds a debuggable (because of the env variable) caddy binary, but doesn’t include the plugin.

I suppose I’ll just stick to the xcaddy build --with stuff, and then launch the debugger by pressing F5 (once that debug launcher has already been selected once).

I hope this helps others who stumble upon this later. While fmt.Printf("\n%v\n", var) may be sufficient for the pros here who already know their way around Caddy and Go, I always get IMMENSE value from stepping through an application in order to see what does what, and when. As such, I also greatly look forward to digging into the guts of Caddy (the Call Stack is a great way to find places to place a breakpoint) to get a better appreciation for how it (and Go itself) works.

1 Like