Files
superset2/.github/workflows/scheduled-docker-image-refresh.yml
Claude Code dfd1a76978 ci: schedule a weekly Docker image rebuild against the latest release
Adds a cron-triggered workflow that re-runs the Docker image build
against the most-recent published release every Monday at 06:00 UTC
(and on manual workflow_dispatch when an operator wants to force it).
The Superset code being built doesn't change — but the base image
layers (python:*-slim-trixie and the Debian OS packages underneath)
DO receive upstream security patches between Superset releases. Without
a rebuild, apache/superset:<latest> ships those CVEs unfixed for as
long as the inter-release gap (typically 3–6 weeks).

Why this approach over the alternatives:

- Tied to releases: defeats the purpose — the gap we're trying to close
  IS the inter-release window. Release-triggered rebuilds happen exactly
  when we already get them.
- Swap to Chainguard / distroless: would also close the gap, but at the
  cost of a backward-incompatible package-manager change for downstream
  operators who extend `apache/superset:<tag>` with their own apt
  install lines for custom drivers. A scheduled rebuild captures most
  of the CVE-cycling benefit without that breakage.
- Daily cadence: probably overkill — Debian's security tree updates on
  a roughly weekly rhythm and a daily rebuild would just churn the
  registry without adding much.

Implementation: deliberately reuses the same `supersetbot docker`
invocation as `tag-release.yml` (same matrix of build presets, same
`--context release --context-ref <tag> --force-latest` flags), so the
resulting tags are byte-equivalent to what a manual release dispatch
would produce — only the base layer changes. Concurrency group
shared with the release publisher so the two can't race each other
on the Docker Hub push.

Tag mutability note: the rebuild overwrites both the rolling tags
(`apache/superset:latest`) AND the version-specific tag of the latest
release (e.g. `apache/superset:5.0.0`). This is intentional and
matches how the upstream `python:*-slim-trixie` images themselves
behave — version tags reflect content + latest patches, not a frozen
SHA. Users who need a frozen reference should pin by image digest.
2026-06-10 16:17:35 -07:00

128 lines
4.7 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Scheduled Docker image refresh
# Re-runs the Docker image build against the latest published release on a
# weekly cadence. The code being built doesn't change — but the base image
# layers (python:*-slim-trixie and its OS packages) DO get upstream
# security patches between Superset releases, and those patches don't
# reach our published images unless we rebuild.
#
# Without this workflow, `apache/superset:<latest>` lags behind upstream
# Debian/Python base patches by whatever interval falls between Superset
# releases (typically 36 weeks). With it, the lag drops to at most one
# week regardless of release cadence.
#
# This is a security-hygiene cron, not a release. It overwrites the
# existing tags for the most recent release (e.g. `apache/superset:5.0.0`
# and `apache/superset:latest`) with bit-for-bit-equivalent contents
# layered on a refreshed base. Image digests change; everything users
# actually pin against (image content, code, deps) does not.
on:
schedule:
# Mondays at 06:00 UTC — gives the weekend for upstream patches to
# settle and surfaces failures at the start of the work week so a
# human can react.
- cron: "0 6 * * 1"
# Manual trigger so operators can force a refresh on demand (e.g.
# immediately after a high-severity base-image CVE drops).
workflow_dispatch: {}
permissions:
contents: read
# Serialize with itself AND with the release publisher — both push to the
# same Docker Hub tags, so a race here could end with stale layers
# winning.
concurrency:
group: docker-publish-latest-release
cancel-in-progress: false
jobs:
config:
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
latest-release: ${{ steps.latest.outputs.tag }}
steps:
- name: Check for Docker Hub secrets
id: check
shell: bash
run: |
if [ -n "${DOCKERHUB_USER}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
env:
DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}
- name: Look up latest published release
id: latest
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# `releases/latest` returns the latest non-prerelease, non-draft
# release — which is exactly what `apache/superset:latest`
# should reflect.
TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name)
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
echo "::error::Could not determine latest release tag"
exit 1
fi
echo "Latest release: $TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
docker-rebuild:
needs: config
if: needs.config.outputs.has-secrets
name: docker-rebuild
runs-on: ubuntu-24.04
strategy:
# Mirror the same matrix the release publisher uses so every variant
# operators consume from Docker Hub gets the refreshed base.
matrix:
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
fail-fast: false
steps:
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ needs.config.outputs.latest-release }}
fetch-depth: 0
persist-credentials: false
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
install-docker-compose: "false"
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
- name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/
- name: Rebuild and push
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Reuses the same supersetbot invocation as the release
# publisher (`tag-release.yml`), so the resulting tags are
# identical to what a manual release dispatch would produce —
# just with a freshly-pulled base image layer underneath.
supersetbot docker \
--push \
--preset ${{ matrix.build_preset }} \
--context release \
--context-ref "${{ needs.config.outputs.latest-release }}" \
--force-latest \
--platform "linux/arm64" \
--platform "linux/amd64"