Build Caddy Docker Image #318
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Caddy Docker Image | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'Dockerfile' | |
| - '.github/workflows/build-docker.yml' | |
| pull_request: | |
| branches: [ main ] | |
| schedule: | |
| - cron: '0 0 * * *' # daily check | |
| workflow_dispatch: | |
| concurrency: | |
| group: caddy-build | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write # needed for auto-commit | |
| packages: write # needed to push to GHCR | |
| jobs: | |
| check-caddy-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| latest: ${{ steps.get.outputs.version }} | |
| changed: ${{ steps.compare.outputs.changed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get latest Caddy release (stable) | |
| id: get | |
| run: | | |
| set -euo pipefail | |
| v=$(curl -fsSL https://api.github.com/repos/caddyserver/caddy/releases/latest \ | |
| | jq -r 'select(.prerelease==false) | .tag_name' | sed 's/^v//') | |
| if [ -z "$v" ] || [ "$v" = "null" ]; then | |
| echo "Failed to resolve latest Caddy release" >&2 | |
| exit 1 | |
| fi | |
| echo "version=$v" >> "$GITHUB_OUTPUT" | |
| - name: Read current version from Dockerfile | |
| id: cur | |
| run: | | |
| set -euo pipefail | |
| cur=$(grep -E '^ARG CADDY_VERSION=' Dockerfile | cut -d= -f2 | tr -d '[:space:]') | |
| if [ -z "$cur" ]; then | |
| echo "Failed to read CADDY_VERSION from Dockerfile" >&2 | |
| exit 1 | |
| fi | |
| echo "version=$cur" >> "$GITHUB_OUTPUT" | |
| - name: Compare | |
| id: compare | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ steps.get.outputs.version }}" != "${{ steps.cur.outputs.version }}" ]; then | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Update Dockerfile if Caddy version changed (direct commit) | |
| # don't auto-push on PRs (read-only token) | |
| if: steps.compare.outputs.changed == 'true' && github.event_name != 'pull_request' | |
| env: | |
| LATEST: ${{ steps.get.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| sed -i "s#^ARG CADDY_VERSION=.*#ARG CADDY_VERSION=${LATEST}#" Dockerfile | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add Dockerfile | |
| git commit -m "chore: bump caddy to ${LATEST}" | |
| git push | |
| check-plugin-pins: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changed: ${{ steps.result.outputs.changed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Read current pins | |
| id: cur | |
| run: | | |
| set -euo pipefail | |
| CF=$(grep -E '^ARG CF_PLUGIN=' Dockerfile | cut -d= -f2- | tr -d '[:space:]') | |
| TS=$(grep -E '^ARG TS_PLUGIN=' Dockerfile | cut -d= -f2- | tr -d '[:space:]') | |
| if [ -z "$CF" ] || [ -z "$TS" ]; then | |
| echo "Failed to read plugin pins from Dockerfile" >&2 | |
| exit 1 | |
| fi | |
| echo "cf=$CF" >> "$GITHUB_OUTPUT" | |
| echo "ts=$TS" >> "$GITHUB_OUTPUT" | |
| echo "Current pins:" | |
| echo " CF_PLUGIN=$CF" | |
| echo " TS_PLUGIN=$TS" | |
| - name: Resolve latest Cloudflare plugin tag (semver) | |
| id: cf | |
| run: | | |
| set -euo pipefail | |
| latest=$(git ls-remote --tags https://github.com/caddy-dns/cloudflare.git \ | |
| | awk '{print $2}' | sed 's#refs/tags/##' \ | |
| | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) | |
| if [ -z "$latest" ]; then | |
| echo "Failed to resolve latest CF tag" >&2 | |
| exit 1 | |
| fi | |
| echo "latest=$latest" >> "$GITHUB_OUTPUT" | |
| echo "Resolved Cloudflare latest tag: $latest" | |
| - name: Resolve latest caddy-tailscale commit on main | |
| id: ts | |
| run: | | |
| set -euo pipefail | |
| sha=$(git ls-remote https://github.com/tailscale/caddy-tailscale.git refs/heads/main | awk '{print $1}') | |
| if [ -z "$sha" ]; then | |
| echo "Failed to resolve latest TS commit" >&2 | |
| exit 1 | |
| fi | |
| echo "latest=$sha" >> "$GITHUB_OUTPUT" | |
| echo "Resolved caddy-tailscale latest commit: $sha" | |
| - name: Update Dockerfile if pins changed (direct commit) | |
| id: result | |
| if: github.event_name != 'pull_request' | |
| env: | |
| CUR_CF: ${{ steps.cur.outputs.cf }} | |
| CUR_TS: ${{ steps.cur.outputs.ts }} | |
| LATEST_CF: github.com/caddy-dns/cloudflare@${{ steps.cf.outputs.latest }} | |
| LATEST_TS: github.com/tailscale/caddy-tailscale@${{ steps.ts.outputs.latest }} | |
| run: | | |
| set -euo pipefail | |
| changed=false | |
| echo "Desired pins:" | |
| echo " CF_PLUGIN=$LATEST_CF" | |
| echo " TS_PLUGIN=$LATEST_TS" | |
| if [ "$CUR_CF" != "$LATEST_CF" ]; then | |
| sed -i "s#^ARG CF_PLUGIN=.*#ARG CF_PLUGIN=${LATEST_CF}#" Dockerfile | |
| changed=true | |
| fi | |
| if [ "$CUR_TS" != "$LATEST_TS" ]; then | |
| sed -i "s#^ARG TS_PLUGIN=.*#ARG TS_PLUGIN=${LATEST_TS}#" Dockerfile | |
| changed=true | |
| fi | |
| echo "changed=$changed" >> "$GITHUB_OUTPUT" | |
| if [ "$changed" = "true" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add Dockerfile | |
| git commit -m "chore: bump plugin pins (cloudflare/tailscale)" | |
| git push | |
| else | |
| echo "Pins already up to date; nothing to commit." | |
| fi | |
| # Ensure 'changed' output exists on PRs too (so needs.* doesn't break) | |
| - name: Set changed=false for PRs | |
| if: github.event_name == 'pull_request' | |
| id: result-pr | |
| run: | | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| build: | |
| runs-on: ubuntu-latest | |
| needs: [check-caddy-version, check-plugin-pins] | |
| # Build on any push/PR; on schedule only if caddy OR plugins changed | |
| if: | | |
| github.event_name != 'schedule' || | |
| needs.check-caddy-version.outputs.changed == 'true' || | |
| needs.check-plugin-pins.outputs.changed == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Critical for Option B: if earlier jobs pushed commits, build must use updated main. | |
| - name: Sync to latest main (schedule only) | |
| if: github.event_name == 'schedule' | |
| run: | | |
| set -euo pipefail | |
| git fetch origin main | |
| git checkout main | |
| git reset --hard origin/main | |
| - name: Read current pins (for labels/trace) | |
| id: pins | |
| run: | | |
| set -euo pipefail | |
| CADDY=$(grep -E '^ARG CADDY_VERSION=' Dockerfile | cut -d= -f2 | tr -d '[:space:]') | |
| CF=$(grep -E '^ARG CF_PLUGIN=' Dockerfile | cut -d= -f2- | tr -d '[:space:]') | |
| TS=$(grep -E '^ARG TS_PLUGIN=' Dockerfile | cut -d= -f2- | tr -d '[:space:]') | |
| echo "caddy=$CADDY" >> "$GITHUB_OUTPUT" | |
| echo "cf=$CF" >> "$GITHUB_OUTPUT" | |
| echo "ts=$TS" >> "$GITHUB_OUTPUT" | |
| echo "Pins -> Caddy: $CADDY" | |
| echo "Pins -> Cloudflare: $CF" | |
| echo "Pins -> Tailscale: $TS" | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64,amd64 | |
| - name: Set up Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags/labels) | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository }} | |
| tags: | | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=raw,value=${{ steps.pins.outputs.caddy }} | |
| type=ref,event=branch | |
| type=sha,prefix=git-,format=short | |
| labels: | | |
| io.github.ayalavalva.caddy.version=${{ steps.pins.outputs.caddy }} | |
| io.github.ayalavalva.plugin.cloudflare=${{ steps.pins.outputs.cf }} | |
| io.github.ayalavalva.plugin.tailscale=${{ steps.pins.outputs.ts }} | |
| - name: Build and push (multi-arch, SBOM/provenance) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: ${{ github.event_name != 'pull_request' }} | |
| platforms: linux/amd64,linux/arm64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: true | |
| sbom: true |