This repository contains the build instructions for a customized Caddy Docker image — a high-performance reverse proxy and web server with automatic HTTPS via Let's Encrypt. It includes the standard Caddy features plus a selection of commonly used plugins for DNS validation, caching, web application firewalling, rate limiting, and more.
The image is automatically built and published to Docker Hub via GitHub Actions.
| Feature | Official caddy |
This Image |
|---|---|---|
| Brotli compression | ❌ | ✅ |
| HTTP caching | ❌ | ✅ Souin (Redis, Otter, SimpleFS) |
| Web application firewall | ❌ | ✅ OWASP Coraza + Core Rule Set (compiled in) |
| Rate limiting | ❌ | ✅ Sliding-window, per-IP/header/host |
| IP range blocking | ❌ | ✅ Defender (AI crawlers, cloud provider ranges) |
| DNS challenge | Requires custom build | ✅ 6 providers built-in |
| Production ready | Needs custom build | ✅ Ready to deploy |
To get started, you need a Caddyfile for your configuration.
docker run -d --name caddy \
-p 80:80 \
-p 443:443 \
-p 443:443/udp \
-v /path/to/your/Caddyfile:/etc/caddy/Caddyfile:ro \
-v caddy_data:/data \
-v caddy_config:/config \
morsalin1342/caddy:latestThis command uses Docker named volumes to persist important data:
caddy_data: Stores TLS certificates and other persistent data. Caddy will create this automatically.caddy_config: Stores any JSON config changes made via the admin API.
Your primary method of configuration is by providing a Caddyfile. Mount your local Caddyfile to /etc/caddy/Caddyfile inside the container as shown in the command above.
For more advanced use cases, you can use the Caddy Admin API.
This image is tagged with two schemes:
morsalin1342/caddy:latest: Always points to the most recently built image from themasterbranch.morsalin1342/caddy:<version>: (e.g.,morsalin1342/caddy:2.11.4) A specific version tag corresponding to the version of Caddy used in the build.
It is recommended to use a specific version tag in production environments for stability.
This image extends the official Caddy image with the following plugins:
- Web Application Firewall (Coraza):
github.com/corazawaf/coraza-caddy/v2- OWASP Coraza is ModSecurity/SecLang compatible. The OWASP Core Rule Set is
compiled into the binary — use
load_owasp_crsand the@-prefixed include paths, with no rule files to download or mount.
- OWASP Coraza is ModSecurity/SecLang compatible. The OWASP Core Rule Set is
compiled into the binary — use
- IP Range Blocking (Defender):
pkg.jsn.cam/caddy-defender- Blocks, drops, or garbles requests from embedded IP range lists — AI crawlers
(OpenAI, Anthropic, Perplexity), cloud providers (AWS, GCP, Azure), VPN exits —
plus your own CIDRs. Note the module path is the vanity import
pkg.jsn.cam/caddy-defender, not the GitHub path.
- Blocks, drops, or garbles requests from embedded IP range lists — AI crawlers
(OpenAI, Anthropic, Perplexity), cloud providers (AWS, GCP, Azure), VPN exits —
plus your own CIDRs. Note the module path is the vanity import
- Rate Limiting:
github.com/mholt/caddy-ratelimit- Sliding-window rate limiting with multiple zones, keyed on any request
placeholder (client IP, host, header). Sets
Retry-Afterautomatically.
- Sliding-window rate limiting with multiple zones, keyed on any request
placeholder (client IP, host, header). Sets
- Brotli Compression:
github.com/dunglas/caddy-cbrotli - HTTP Caching (Souin):
github.com/darkweak/souin/plugins/caddy- Storage backends:
github.com/darkweak/storages/go-redis/caddy(Redis)github.com/darkweak/storages/otter/caddy(in-memory)github.com/darkweak/storages/simplefs/caddy(file-based)
- Storage backends:
- DNS Providers for ACME TLS:
github.com/caddy-dns/vultrgithub.com/caddy-dns/azuregithub.com/caddy-dns/googleclouddnsgithub.com/caddy-dns/digitaloceangithub.com/caddy-dns/cloudflaregithub.com/caddy-dns/route53
Q: How do I add more plugins?
A: Fork this repository and add your desired plugins to the xcaddy build command in the Dockerfile, then rebuild. Caddy plugins are Go modules compiled directly into the binary.
Q: How do I use DNS challenge for wildcard certificates?
A: All six DNS providers (Cloudflare, Route53, DigitalOcean, Vultr, Azure, Google Cloud DNS) are pre-compiled. Set the appropriate environment variables for your provider's API credentials and configure the tls directive in your Caddyfile.
Q: How does caching work with this image?
A: The Souin cache handler is pre-installed with three storage backends: simplefs (file-based, zero config), go-redis (distributed), and otter (in-memory). Configure caching in your Caddyfile via the cache directive.
Q: How do I turn the WAF on?
A: Add order coraza_waf first to your global options, then a coraza_waf block to
the site. load_owasp_crs is what makes the @ paths resolvable — without it Caddy
fails at startup with open @coraza.conf-recommended: no such file or directory.
{
order coraza_waf first
}
example.com {
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine DetectionOnly
`
}
reverse_proxy localhost:8080
}Start at SecRuleEngine DetectionOnly, watch the logs for false positives, write your
exclusions, and only then switch to On. CRS in blocking mode will otherwise break
application admin panels — WordPress's /wp-admin in particular.
Q: How do I rate limit login endpoints?
A: rate_limit is ordered before basic_auth by default, so no order directive is
needed. Matchers go inside the zone's match block:
example.com {
rate_limit {
zone login {
match {
path /wp-login.php /xmlrpc.php
}
key {http.request.remote.host}
events 5
window 1m
}
}
reverse_proxy localhost:8080
}window and events are both required. A key containing a placeholder allocates one
limiter per distinct value — here, per client IP. A static key (no placeholder) means one
limiter for the whole zone. Retry-After is set automatically on the 429.
Q: How do I block AI crawlers?
A: defender matches the client IP against embedded range lists, refreshed with the
module rather than fetched at runtime:
example.com {
defender block {
ranges aws azurepubliccloud deepseek gcloud githubcopilot openai
}
reverse_proxy localhost:8080
}Those six are also the default if you omit ranges. Custom CIDRs can be listed alongside
the predefined keys. Responders: block (403), drop, garbage, tarpit, custom
(needs message), redirect (needs url), and ratelimit, which hands the request to
the rate limit module above rather than rejecting it outright.
Matching is by IP range, so this catches declared crawler infrastructure — not a scraper that spoofs a user agent from a residential address.
Q: Can I use this image with FrankenPHP? A: This is a standalone Caddy image. For a pre-configured Caddy+PHP setup, use the morsalin1342/frankenphp image instead, which bundles this same Caddy plugin stack with PHP.
Every image is published to both the personal and the organization namespace, from the same build.
| Repository | Images | Description |
|---|---|---|
| frankenphp-docker | morsalin1342/frankenphp · easydigital/frankenphp |
Caddy + PHP app server in one container |
| php-docker | morsalin1342/php · easydigital/php |
Traditional PHP-FPM & CLI images |
| nginx-docker | morsalin1342/nginx · easydigital/nginx |
nginx with ModSecurity 3, Brotli, zstd & GeoIP2 |
| caddy-souin-cache-manager | — | WordPress plugin to manage this image's Souin cache from WP Admin |
If you have suggestions or find a bug, please open an issue on the GitHub repository.
⭐ If this project helps you, consider giving it a star!