Custom Caddy images with curated module sets, published to GHCR with a public Docker Hub mirror and rebuilt automatically when upstream components change.
This repository provides ready-to-use Caddy container images for self-hosted and infrastructure setups that need more than the official default image. Each image variant is built with a defined module set, tracked against upstream changes, and published with reproducible tags plus traceable metadata.
Registries:
- GHCR (canonical):
ghcr.io/smoochy/caddy-cloudflare-modules - Docker Hub (public mirror):
smoochy84/caddy-cloudflare-modules
If this project saves you time or helps your setup, you can support ongoing maintenance for a project I maintain in my spare time via Ko-fi or Buy Me a Coffee.
- Background
- Available Images
- Cloudflare Image
- When Builds Run
- Dockerfile
- GitHub Actions Workflow
- Job Summary
- Image Metadata
- Image Tags
- Install
- Usage
- Transparency
- Adding More Addons
- Adding a New Image Variant
- Security
- Maintainers
- Contributing
- License
The official Caddy image is intentionally minimal. This repository provides maintained custom image variants for setups that rely on additional modules and want a reproducible way to stay current with upstream changes.
| Image | Dockerfile | Workflow | Description |
|---|---|---|---|
caddy-cloudflare-modules |
Dockerfile-cloudflare |
build_cloudflare-modules.yaml |
Cloudflare DNS and IP modules |
Additional variants can be added at any time. See Adding a New Image Variant.
| Addon | Purpose |
|---|---|
caddy-dns/cloudflare |
DNS-01 ACME challenge provider for Cloudflare |
WeidiDeng/caddy-cloudflare-ip |
Provides the real client IP when behind Cloudflare proxy |
fvbommel/caddy-combine-ip-ranges |
Combines multiple IP range sources for trusted proxies |
lucaslorentz/caddy-docker-proxy |
Automatic Caddy configuration via Docker labels |
- Builds the custom Docker image
caddy-cloudflare-modules - Publishes the image to:
- GHCR (canonical):
ghcr.io/smoochy/caddy-cloudflare-modules:latestghcr.io/smoochy/caddy-cloudflare-modules:caddy-<x.y.z>
- Docker Hub (public mirror):
smoochy84/caddy-cloudflare-modules:latestsmoochy84/caddy-cloudflare-modules:caddy-<x.y.z>
- GHCR (canonical):
- Tracks upstream updates and rebuilds only when needed
The workflow is triggered in three ways:
-
Push to
main, but only when one of these files changes:Dockerfile*.dockerignore.github/workflows/build_*.yaml
This prevents rebuilds for documentation-only changes such as
README.md. -
Scheduled run:
- Runs a daily check at 03:00 UTC for upstream changes such as the Caddy base digest and addon versions
- Builds only if something changed
-
Manual run with
workflow_dispatch:- Optional
force=trueinput to rebuild even if nothing changed
- Optional
The Dockerfile:
- Uses a two-stage build
- Uses
caddy:builderandxcaddyto compile Caddy with addons - Uses
caddy:latestas the final base image - Supports multi-arch builds for:
linux/amd64linux/arm64
- Declares addons via
--witharguments
Commented-out --with lines are ignored by both the build and the workflow.
build_*.yaml does the following:
- Logs into GHCR and sets up Buildx plus QEMU.
- Parses the Dockerfile to discover active addons from
--withlines. - Fetches upstream versions using authenticated GitHub API calls.
- Reads metadata from the currently published image when it exists.
- Compares the Caddy base digest and addon versions.
- Builds once, pushes to GHCR, and mirrors the published tags to Docker Hub when Docker Hub secrets are configured.
- Publishes only when:
- a push-triggered run happens
- an upstream change is detected
- a manual run is forced
Every workflow run writes a summary that includes:
- The reason the build ran
- Which upstream component changed
- The Caddy base digest and version
- All addon versions with direct links
- Changelogs from upstream release notes where available
- The published image tags
- Whether the Docker Hub mirror was updated or skipped
Each published image includes OCI labels used for traceability and change detection, for example:
org.opencontainers.image.base.tagorg.opencontainers.image.base.digestorg.opencontainers.image.base.versionorg.opencontainers.image.cloudflare.versionorg.opencontainers.image.addon.N.nameorg.opencontainers.image.addon.N.version
This image is published with:
latest: always points to the newest buildcaddy-<x.y.z>: matches the Caddy base version used at build time and is useful for reproducible deployments pinned to a specific Caddy release
Pull the published image from GHCR (canonical) or Docker Hub (public mirror):
docker pull ghcr.io/smoochy/caddy-cloudflare-modules:latestdocker pull smoochy84/caddy-cloudflare-modules:latestFor reproducible deployments, pin both a version tag and digest:
ghcr.io/smoochy/caddy-cloudflare-modules:caddy-<x.y.z>@sha256:<digest>
The digest of every published image is visible in the GitHub Actions Job Summary and on the GHCR package page. The same version tags are mirrored to Docker Hub at smoochy84/caddy-cloudflare-modules.
Example docker-compose.yml:
services:
caddy:
image: ghcr.io/smoochy/caddy-cloudflare-modules:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
volumes:
caddy_data:
caddy_config:Example Caddyfile:
{
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
example.com {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
respond "Hello from Caddy with Cloudflare addons!"
}The code, documentation, and related project materials in this repository were created and refined with AI assistance. All generated output was reviewed and adapted before publication.
To include another Caddy module, add a --with line to Dockerfile-cloudflare:
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/WeidiDeng/caddy-cloudflare-ip \
--with github.com/fvbommel/caddy-combine-ip-ranges \
--with github.com/lucaslorentz/caddy-docker-proxy/v2 \
--with github.com/your-org/your-caddy-addonThe workflow automatically picks up new --with lines and:
- Fetches the latest version
- Includes it in the Job Summary and OCI labels
- Tracks it for future upstream change detection
Each image variant is self-contained and consists of exactly two files:
| File | Purpose |
|---|---|
Dockerfile-<name> |
Defines the modules compiled into the image |
.github/workflows/build_<name>.yaml |
Builds, tags, and publishes the image |
To add a new variant:
- Copy
Dockerfile-cloudflaretoDockerfile-<name>and adjust the--withlines. - Copy
.github/workflows/build_cloudflare-modules.yamlto.github/workflows/build_<name>.yaml. - Update the hardcoded image name in the new workflow.
- Update the
file:reference fromDockerfile-cloudflaretoDockerfile-<name>. - Add the new image to the table above.
- No secrets are baked into the image
- GitHub Actions uses the built-in
GITHUB_TOKENfor GHCR authentication and GitHub API calls - Optional Docker Hub publishing uses
DOCKERHUB_USERNAMEandDOCKERHUB_TOKENrepository secrets - Only release metadata is queried from upstream projects
- smoochy
Issues and pull requests are welcome. Keep image, workflow, and documentation changes aligned so the published image behavior stays obvious from the README.