This repository builds and publishes a custom hardened multi-arch Caddy image compiled with xcaddy. Designed for Docker-based homelab and VPS deployments using label-driven reverse proxying, Cloudflare DNS automation, and a fully automated weekly rebuild pipeline.
Published to:
- GHCR:
ghcr.io/atnplex/caddy - DockerHub:
atnplex/caddy
Architectures: linux/amd64 · linux/arm64
Use
latestfor automatic updates via Watchtower or Diun. Use a digest-pinned tag for fully reproducible deployments — see Releases.
docker run -d \
--name caddy \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
--group-add $(stat -c '%g' /var/run/docker.sock) \
-p 80:80 \
-p 443:443 \
ghcr.io/atnplex/caddy:latestservices:
caddy:
image: ghcr.io/atnplex/caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
- TZ=America/Los_Angeles
env_file:
- /etc/caddy/.env
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- caddy_data:/data
- caddy_config:/config
group_add:
- "${DOCKER_GID:-999}"
networks:
- atn_bridge
volumes:
caddy_data:
caddy_config:
networks:
atn_bridge:
external: trueSet
DOCKER_GIDto your host's docker group GID:stat -c '%g' /var/run/docker.sock
| Module | Purpose |
|---|---|
| caddy-docker-proxy | Builds Caddy config dynamically from Docker container labels |
| caddy-dns/cloudflare | Cloudflare DNS-01 ACME certificate automation |
| caddy-cloudflare-ip | Restores real client IPs from Cloudflare CDN headers |
| cache-handler | HTTP response caching |
| transform-encoder | Custom log format transformation |
| caddy-security | Authentication and authorization portal |
labels:
caddy: myapp.example.com
caddy.reverse_proxy: "{{upstreams 8080}}"
caddy.tls.dns: cloudflare {$CLOUDFLARE_API_TOKEN}
caddy.tls.resolvers: 1.1.1.1| Tag | Registry | Description |
|---|---|---|
latest |
GHCR · DockerHub | Always points to the most recent successful build |
2 · 2.11 |
GHCR only | Floating semver major / minor tags — updated on every build |
2.11.2 |
GHCR · DockerHub | Patch-level version pin |
2.11.2-2026.03.28 |
GHCR · DockerHub | Version + build date for fully reproducible deployments |
sha-abc1234 |
GHCR · DockerHub | Git SHA of the Dockerfile at build time |
Note: Floating major/minor tags (
2,2.11) are only pushed to GHCR. DockerHub receives only immutable tags (patch version, date-stamped, and SHA) to prevent silent overwrites for users who may have pinned these tags in compose files expecting stability.For production deployments requiring full immutability, pin to a digest from the Releases page.
Every image is stamped with the following OCI-compliant labels at build time:
| Label | Description |
|---|---|
org.opencontainers.image.version |
Caddy version (e.g. 2.11.2) |
org.opencontainers.image.created |
Build date in YYYY.MM.DD format |
org.opencontainers.image.revision |
Short Git SHA of the Dockerfile commit |
org.opencontainers.image.source |
Link to this repository |
org.atnplex.build.fingerprint |
Pipe-delimited string of all resolved component versions — used by the build pipeline to detect changes and skip unnecessary rebuilds |
Inspect labels on any pulled image:
docker inspect ghcr.io/atnplex/caddy:latest \
--format '{{ json .Config.Labels }}' | jqThis image is designed for a pull-based GitOps pattern.
Watchtower — automatic pull and restart:
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 86400 --cleanupDiun — notification only, no auto-restart:
Monitors ghcr.io/atnplex/caddy:latest and notifies when a new digest is published.
Builds run automatically every Monday at 02:00 PST or on manual trigger.
The workflow:
- Resolves latest stable versions of Caddy and all 6 modules from upstream
- Compares resolved versions against the fingerprint on the currently published image
- Skips if nothing meaningful changed — idempotent, no redundant rebuilds
- Builds multi-arch image (amd64 + arm64)
- Runs smoke tests and verifies all 6 modules are present
- Scans with Docker Scout — advisory only, does not block the build; CVE summary appears in the workflow step summary
- Pushes to GHCR and DockerHub simultaneously with SBOM and provenance attestation
- Signs with cosign keyless OIDC
- Creates a GitHub Release with full component versions and image digests
Complete these one-time setup steps before the workflow can run:
In Settings → Secrets and variables → Actions, add one secret:
BWS_ACCESS_TOKEN— your Bitwarden Secrets Manager access token
DockerHub credentials are fetched from Bitwarden at runtime.
GITHUB_TOKEN is provided automatically by GitHub Actions.
Cosign uses keyless OIDC signing — no key secrets needed.
Install the Renovate GitHub App on this repository. Renovate automatically opens pull requests when new stable versions of base images or Go modules are detected. Patch and minor PRs are configured to automerge. The next scheduled workflow run detects the updated Dockerfile fingerprint and triggers a rebuild automatically.
Confirm your personal DockerHub PAT stored in Bitwarden has write access
to the atnplex org namespace. The workflow pushes to atnplex/caddy
using your personal account credentials.
In Settings → Branches, protect the main branch and allow the
Renovate bot to bypass protection rules for automerge to work.
Set the repository About section via Settings → General → Description (or the gear icon on the repo homepage):
- Description:
Hardened multi-arch custom Caddy image with docker-proxy, Cloudflare DNS/IP, cache-handler, transform-encoder, and caddy-security modules - Website:
https://hub.docker.com/r/atnplex/caddy - Topics:
caddy,docker,reverse-proxy,cloudflare,homelab,xcaddy,github-actions
Each successful build creates a GitHub Release containing full component version details, image digests for both architectures, digest-pinned pull commands, and a Docker Scout security summary.
