I recently had a requirement where I needed Caddy to inject a custom JavaScript snippet into the HTML responses of a proxied web service. After some research, I found that this can be achieved by installing a specific Caddy module. Since I hadn’t worked with Caddy modules before, I spent some time learning how to install and configure them.
Caddy’s Modular Architecture
Caddy is built as a collection of modules. While the default distribution includes the most common features, it doesn’t include everything—even official modules. To add a specific module, you must recompile Caddy with that module included.
Checking Installed Modules
You can verify which modules are currently active in your Caddy binary using the following commands:
# List all installed modules
caddy list-modules
# Search for a specific module (e.g., 'replace')
caddy list-modules | grep replace
Customizing Caddy via Docker
I needed the replace-response module. Since I run Caddy via Docker, the best approach was to create a custom Docker image using a multi-stage build. Here is the Dockerfile:
# Use the official Caddy builder image
FROM caddy:2-builder AS builder
# Build Caddy with the desired external module
RUN xcaddy build \
--with github.com/caddyserver/replace-response
# Switch to the standard Caddy runtime image
FROM caddy:2
# Replace the default binary with the custom-built one
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
Run docker build -t my-caddy . to build the image. You will then have a my-caddy image that includes the replace-response functionality.
Final Thoughts
By following these steps, we successfully added a custom module to Caddy. Using a Docker multi-stage build is an efficient way to manage dependencies and ensures that your custom Caddy environment remains consistent across different deployments.

