This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch ci/scheduled-docker-image-refresh in repository https://gitbox.apache.org/repos/asf/superset.git
commit 3212243906c14a844038d0b27ac8b9dcac8b4f20 Author: Claude Code <[email protected]> AuthorDate: Mon May 25 20:26:15 2026 -0700 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. --- .../workflows/scheduled-docker-image-refresh.yml | 127 +++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/.github/workflows/scheduled-docker-image-refresh.yml b/.github/workflows/scheduled-docker-image-refresh.yml new file mode 100644 index 00000000000..69d80440c42 --- /dev/null +++ b/.github/workflows/scheduled-docker-image-refresh.yml @@ -0,0 +1,127 @@ +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 3–6 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"
