Skip to content

About

Shared build and release automation for CaddyBuilds Docker images

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

18 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

caddy-base

Shared build and release automation for the CaddyBuilds Docker images: caddy-cloudflare, caddy-bunny, caddy-namecheap.

Each image repo keeps what makes it different: its Dockerfile (the module list), a small caddy-image.json, its README and example Caddyfiles. Everything about how an image is checked, built, tagged, verified and released lives here and is pulled in as reusable workflows, so a fix lands in every repo on its next run.

What is here

Path Purpose
.github/workflows/build-image.yml Reusable: multi-platform build, push to GHCR and Docker Hub, verify, GitHub Release, failure issue
.github/workflows/check-release.yml Reusable: daily check for new Caddy releases, module commits and republished official images; dispatches a build
scripts/check_caddy_status/ The checker. Reads the image repo's Dockerfile and caddy-image.json, tracks module commits in module-versions.json
Dockerfile The caddy-base image: Alpine with everything the official caddy image has except the Caddy binary
.github/workflows/build-base-image.yml Builds and publishes ghcr.io/caddybuilds/caddy-base; daily rebuild when the pinned Alpine tag's digest changes
examples/ A complete image repo skeleton: Dockerfile, caddy-image.json, the two caller workflows

Using it in an image repo

An image repo needs four files besides its README.

Dockerfile, complete and buildable on its own. The checker reads the modules from the xcaddy build line, so this is the only place they are listed. Name the main stage default and keep it last, so a plain docker build . produces it. A slim stage is optional: FROM ghcr.io/caddybuilds/caddy-base:latest and copy the binary from the builder (see examples/Dockerfile). The checker reads the slim base from that FROM line. The build workflow builds each stage by name, and slim only when the config enables it.

ARG CADDY_VERSION=2
FROM --platform=$BUILDPLATFORM caddy:${CADDY_VERSION}-builder AS builder
ARG TARGETOS TARGETARCH TARGETVARIANT
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH GOARM=${TARGETVARIANT#v} CGO_ENABLED=0 XCADDY_PRINT_VERSION=0 \
    xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:${CADDY_VERSION} AS default
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

The builder stage runs on the build host and cross-compiles, so the Go build is never emulated; only the final COPY stage runs per platform. See examples/Dockerfile.

caddy-image.json, the values the workflows cannot derive:

{
  "title": "Caddy Cloudflare",
  "description": "Caddy with Cloudflare DNS module",
  "verify_module": "dns.providers.cloudflare",
  "alpine_alias": true,
  "slim": false,
  "create_release": true,
  "platforms": ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/ppc64le", "linux/s390x"]
}
Key Required Default Meaning
title yes org.opencontainers.image.title
description yes org.opencontainers.image.description, also used in release notes
verify_module yes Module ID that must appear in caddy list-modules after the build
alpine_alias no true Also push X.Y.Z-alpine, X.Y-alpine, X-alpine and alpine as aliases of the default image
slim no false Also build the Dockerfile's slim stage and push X.Y.Z-slim, X.Y-slim, X-slim, slim
create_release no true Create a GitHub Release per Caddy version and append rebuild notes to it
platforms no the five above Platforms to build, verify and require upstream. Any non-empty subset of what the official image publishes: linux/amd64, linux/arm64, linux/arm/v6, linux/arm/v7, linux/ppc64le, linux/riscv64, linux/s390x

The schema lives in scripts/check_caddy_status/image_config.py; the build workflow runs that module to read the file, so the checker and the build always agree on it.

Two caller workflows, copied from examples/.github/workflows/. A manual build takes an optional note, recorded in the release notes and the caddy-build.details label so a rebuild can be traced to its cause:

jobs:
  build:
    uses: CaddyBuilds/caddy-base/.github/workflows/build-image.yml@v1
    with:
      caddy_version: ${{ github.event.inputs.build_version || github.event.client_payload.caddy_version }}
      build_reason: ${{ github.event.client_payload.build_reason || 'manual' }}
      build_details: ${{ github.event.inputs.note || github.event.client_payload.build_details || '' }}
    secrets: inherit
jobs:
  check:
    uses: CaddyBuilds/caddy-base/.github/workflows/check-release.yml@v1
    secrets: inherit

Image names come from the repository name (ghcr.io/<owner>/<repo>), so a fork of any image repo builds and publishes its own image without changing anything here. Docker Hub is used when DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets exist; DOCKERHUB_REPOSITORY_NAME (secret or variable) overrides the Docker Hub repository.

The caddy-base image

ghcr.io/caddybuilds/caddy-base is the official caddy image's Alpine layer without the Caddy binary: the same apk packages (ca-certificates, curl, libcap, mailcap), directories, default Caddyfile, welcome page, environment, ports, workdir and CMD, built from the Dockerfile at the repo root for every platform the official image publishes. An image repo's slim stage copies its custom-built binary onto it, so the slim image carries one Caddy binary instead of the stock one plus ours.

Tag Meaning
latest Floating; what slim stages reference
alpine3.23 The Alpine release pinned in the Dockerfile's FROM line

The build runs when the Dockerfile changes and daily at 21:00 UTC, three hours before the image repos' checks. Each image records the alpine digest it was built from (org.opencontainers.image.base.digest). The daily run rebuilds only when library/alpine:<pin> now points at a different digest, so Alpine fixes reach the slim images on the next day's check without pinning package versions. The pin is bumped by editing the FROM line.

How the check decides to build

  1. Fetch the latest Caddy release and confirm the official image has every platform we ship.
  2. If our image for that version is missing a platform on GHCR, build (new_caddy_version).
  3. Compare each module's latest commit with module-versions.json; on change, build (module_update) and commit the new state.
  4. With slim enabled, if the X.Y.Z-slim tag is missing or incomplete, build (missing_variant).
  5. Compare each variant's base image with our build time, looking only at the platforms we ship: the default image against library/caddy:X.Y.Z on Docker Hub, the slim image against the base named in its Dockerfile stage (the caddy-base image on GHCR). If a base was republished, build (base_image_update).

The reason and details are passed to the build and recorded in the image labels (caddy-build.reason, caddy-build.details, caddy-build.base-workflow) and the release notes.

Versioning

Tags follow semver. v1 is a floating tag that always points at the latest v1.x.y; image repos pin @v1 and pick up fixes automatically. A breaking change to inputs, the config schema or the Dockerfile contract gets a new major and a one-line change in each caller.

The check workflow checks out this repo at the exact commit of the workflow being run, so the Python package always matches the workflow version the caller selected.

Development

cd scripts
python -m unittest discover -s tests -t . -v

# dry run against an image repo checkout
cd /path/to/caddy-cloudflare
PYTHONPATH=/path/to/caddy-base/scripts GITHUB_REPOSITORY=owner/repo \
  python -m check_caddy_status --dry-run


# build the caddy-base image locally
docker buildx build --platform linux/arm64 --load -t ghcr.io/caddybuilds/caddy-base:latest .

License

MIT, see LICENSE.

About

Shared build and release automation for CaddyBuilds Docker images

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages