Skip to content

Build Caddy Docker Image #318

Build Caddy Docker Image

Build Caddy Docker Image #318

Workflow file for this run

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