Skip to content

Rebuild Docker Image on Base Update #12

Rebuild Docker Image on Base Update

Rebuild Docker Image on Base Update #12

name: Rebuild Docker Image on Base Update
on:
schedule:
- cron: '0 7 * * 1' # weekly, Monday 07:00 UTC
workflow_dispatch:
permissions:
contents: read # checkout only; Docker Hub auth uses secrets, not GITHUB_TOKEN
jobs:
check_base_image:
name: Check for base image updates
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
rebuild: ${{ steps.compare.outputs.rebuild }}
upstream_digest: ${{ steps.upstream.outputs.digest }}
release_tag: ${{ steps.release.outputs.tag }}
steps:
- name: Check out the repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
- name: Determine latest release tag
id: release
run: |
TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1)
if [ -z "$TAG" ]; then
echo "No v* release tag found" >&2
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Get current upstream base digest
id: upstream
run: |
DIGEST=$(docker buildx imagetools inspect node:lts-alpine --format '{{json .Manifest}}' | jq -r '.digest')
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
- name: Get published image base digest
id: published
run: |
# The label lives in each platform image config; handle single- and multi-platform output shapes.
DIGEST=$(docker buildx imagetools inspect isokoliuk/mcp-searxng:latest --format '{{json .Image}}' \
| jq -r '[.. | objects | select(has("config")) | .config.Labels["org.opencontainers.image.base.digest"] // empty] | first // ""')
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
- name: Compare digests
id: compare
run: |
UPSTREAM="${{ steps.upstream.outputs.digest }}"
PUBLISHED="${{ steps.published.outputs.digest }}"
echo "Upstream base: $UPSTREAM"
echo "Published base: ${PUBLISHED:-<no base digest label>}"
if [ "$UPSTREAM" != "$PUBLISHED" ]; then
echo "Base image drift detected — rebuild required"
echo "rebuild=true" >> "$GITHUB_OUTPUT"
else
echo "Published image is up to date"
echo "rebuild=false" >> "$GITHUB_OUTPUT"
fi
rebuild_scan:
name: Scan rebuilt Docker image
needs: check_base_image
if: needs.check_base_image.outputs.rebuild == 'true'
runs-on: ubuntu-latest
permissions:
contents: read # checkout only
security-events: write # for Trivy SARIF upload to GitHub Security tab
steps:
- name: Check out latest release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.check_base_image.outputs.release_tag }}
- name: Patch Dockerfile to current upstream base digest
run: |
sed -i -E "s|node:lts-alpine(@sha256:[a-f0-9]{64})?|node:lts-alpine@${{ needs.check_base_image.outputs.upstream_digest }}|g" Dockerfile
echo "Patched FROM lines:"
grep -n '^FROM' Dockerfile
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build image for vulnerability scan (amd64)
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
load: true
tags: isokoliuk/mcp-searxng:rebuild-scan
- name: Scan rebuilt image for vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'isokoliuk/mcp-searxng:rebuild-scan'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
exit-code: '1'
- name: Upload Trivy scan results to Security tab
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
if: always()
with:
sarif_file: 'trivy-results.sarif'
rebuild_and_publish:
name: Rebuild and republish with updated base
needs:
- check_base_image
- rebuild_scan
if: needs.check_base_image.outputs.rebuild == 'true'
runs-on: ubuntu-latest
permissions:
contents: read # checkout only; Docker Hub auth uses secrets, not GITHUB_TOKEN
id-token: write # for Cosign keyless signing
steps:
- name: Check out latest release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.check_base_image.outputs.release_tag }}
- name: Patch Dockerfile to current upstream base digest
run: |
sed -i -E "s|node:lts-alpine(@sha256:[a-f0-9]{64})?|node:lts-alpine@${{ needs.check_base_image.outputs.upstream_digest }}|g" Dockerfile
echo "Patched FROM lines:"
grep -n '^FROM' Dockerfile
- name: Compute version tags
id: tags
run: |
TAG="${{ needs.check_base_image.outputs.release_tag }}"
VERSION="${TAG#v}"
MAJOR_MINOR="${VERSION%.*}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "major_minor=$MAJOR_MINOR" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# A failed Trivy scan (exit-code 1) stops the scan job, blocking the push below.
- name: Build and push multi-arch image
id: publish
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
isokoliuk/mcp-searxng:${{ steps.tags.outputs.version }}
isokoliuk/mcp-searxng:${{ steps.tags.outputs.major_minor }}
isokoliuk/mcp-searxng:latest
labels: |
org.opencontainers.image.base.name=docker.io/library/node:lts-alpine
org.opencontainers.image.base.digest=${{ needs.check_base_image.outputs.upstream_digest }}
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: 'v3.1.1'
- name: Sign image (keyless OIDC)
env:
DIGEST: ${{ steps.publish.outputs.digest }}
run: cosign sign --yes "docker.io/isokoliuk/mcp-searxng@${DIGEST}"