A batteries-included Docker image of Caddy preloaded with useful plugins for running a secure, production-ready reverse proxy.
Think of it as your site's doorman — it stands at the door, checks IDs (defender), enforces the rules (rate limiting), and handles the paperwork (automatic HTTPS via DNS-01).
Building custom Caddy images with xcaddy every time a new version drops (or a plugin updates) is annoying. This image gives you a curated, opinionated set of plugins that most self-hosters actually need for public-facing services:
- Strong bot/scraper protection
- Abuse prevention via rate limiting
- Reliable wildcard / multi-domain HTTPS using DNS challenges
| Plugin | Purpose |
|---|---|
| caddy-defender | Block or tarpit requests from AI scrapers, cloud providers (AWS, GCP, Azure, etc.), and custom IP ranges. Great for protecting your content from being used to train models. |
| caddy-ratelimit | Flexible, high-performance HTTP rate limiting (including distributed via Redis if you need it). |
| caddy-dns/namecheap | Namecheap DNS provider for ACME DNS-01 challenges (Let's Encrypt / ZeroSSL). Use this when you manage DNS at Namecheap. |
| caddy-dns/cloudflare | Cloudflare DNS provider for ACME DNS-01 challenges. The most common choice for wildcard / multi-domain certificates. |
| caddy-ip-list | Dynamic IP-range source for trusted_proxies. Fetches and refreshes Cloudflare's edge ranges so {client_ip} resolves to the real visitor IP behind the proxy. |
The image is rebuilt automatically every month (to refresh plugins) and whenever the Caddy base version changes.
docker pull ghcr.io/tomholford/caddy-doorman:latestdocker run -d \
--name caddy-doorman \
-p 80:80 -p 443:443 -p 443:443/udp \
-v $PWD/Caddyfile:/etc/caddy/Caddyfile \
-v caddy-data:/data \
-v caddy-config:/config \
-e NAMECHEAP_API_KEY=your_key \
-e NAMECHEAP_API_USER=your_username \
ghcr.io/tomholford/caddy-doorman:latestversion: "3.8"
services:
caddy:
image: ghcr.io/tomholford/caddy-doorman:latest
container_name: caddy-doorman
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
environment:
- NAMECHEAP_API_KEY=${NAMECHEAP_API_KEY}
- NAMECHEAP_API_USER=${NAMECHEAP_API_USER}
# Optional: if you want to use defender's real client IP detection
# extra_hosts:
# - "host.docker.internal:host-gateway"
volumes:
caddy-data:
caddy-config:{
# Global options
email you@example.com
}
example.com {
# Use Namecheap for DNS challenges
tls {
dns namecheap {
api_key {env.NAMECHEAP_API_KEY}
user {env.NAMECHEAP_API_USER}
}
}
reverse_proxy backend:8080
}Required environment variables for Namecheap:
NAMECHEAP_API_KEYNAMECHEAP_API_USER
Get them from your Namecheap account → Domain List → Advanced DNS → Dynamic DNS.
example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy backend:8080
}Required environment variable for Cloudflare:
CF_API_TOKEN— a Cloudflare API token withZone:DNS:Editpermission for the zone.
If you run behind Cloudflare's proxy, declare it as a trusted proxy so {client_ip} (used by rate limiting and defender) resolves to the real visitor IP rather than Cloudflare's edge. The bundled caddy-ip-list plugin fetches and refreshes Cloudflare's ranges automatically:
{
servers {
trusted_proxies list {
url https://www.cloudflare.com/ips-v4
url https://www.cloudflare.com/ips-v6
interval 12h
timeout 15s
}
client_ip_headers CF-Connecting-IP
}
}{
order defender before reverse_proxy
}
example.com {
# The first token is the responder: block | tarpit | garbage | drop |
# redirect | custom | ratelimit. Mix predefined range keys with your own CIDRs.
defender block {
ranges openai aws gcloud azurepubliccloud githubcopilot deepseek 203.0.113.0/24
}
reverse_proxy backend:8080
}See the caddy-defender documentation for all available ranges and responder options.
{
order rate_limit before reverse_proxy
}
example.com {
rate_limit {
zone perip {
key {client_ip}
events 100
window 1m
}
}
reverse_proxy backend:8080
}Full options in the caddy-ratelimit docs. Zones can also match on path/method for finer-grained limits.
See
examples/Caddyfilefor one config that combines DNS certs, defender, rate limiting, and Cloudflare real-IP — it's validated in CI.
latest— newest build (latest stable Caddy + current plugins)2— latest 2.x build2.11— latest 2.11.x build2.11.4— a specific Caddy version (plugins as of that build)
Plugins are unpinned, so the monthly rebuild refreshes them under the same tags. All images are multi-arch (linux/amd64, linux/arm64).
Clone and build the image (it's a thin xcaddy wrapper — see Dockerfile):
git clone https://github.com/tomholford/caddy-doorman
cd caddy-doorman
docker build -t caddy-doorman .Or build just the binary with xcaddy. Note caddy-defender's module path is the vanity import pkg.jsn.cam/caddy-defender:
xcaddy build \
--with pkg.jsn.cam/caddy-defender \
--with github.com/mholt/caddy-ratelimit \
--with github.com/caddy-dns/namecheap \
--with github.com/caddy-dns/cloudflare \
--with github.com/monobilisim/caddy-ip-list- Pin plugin versions for fully reproducible builds (Renovate Go-module manager)
- Publish to additional registries (Docker Hub, Quay)
- More pre-baked example Caddyfiles for common stacks (Nextcloud, *arr, etc.)
- Prometheus metrics + health check examples
Issues and PRs welcome! This project aims to stay small and opinionated — the goal is "the plugins most people need for a secure public reverse proxy" rather than "everything."
Apache 2.0 (same as Caddy).
caddy-doorman — your site's friendly but firm doorman. 🛡️
Not affiliated with the official Caddy project.