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:` 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 (tag-release.yml) — # both push to the same Docker Hub tags, so a race could end with stale # layers winning. Both workflows must declare this group for the lock to work. 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 }} force-latest: ${{ steps.latest.outputs.force-latest }} 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 }} REPOSITORY: ${{ github.repository }} run: | # `releases/latest` returns the latest non-prerelease, non-draft # release — which is exactly what `apache/superset:latest` # should reflect. TAG=$(gh api "repos/${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" # Only move `:latest` when the release flagged "latest" is also the # highest semver release. This guards against a mis-click leaving an # older maintenance release (e.g. a 5.x patch shipped after 6.0 GA) # marked latest, which would otherwise roll `:latest` back a major # version on the next cron run. If it isn't the newest, we still # refresh that release's own version tag but leave `:latest` alone. HIGHEST=$(gh api --paginate "repos/${REPOSITORY}/releases" \ --jq '.[] | select(.draft|not) | select(.prerelease|not) | .tag_name' \ | sed 's/^v//' | sort -V | tail -n1) if [ "${TAG#v}" = "$HIGHEST" ]; then echo "force-latest=1" >> "$GITHUB_OUTPUT" else echo "::warning::Latest-flagged release $TAG is not the highest semver ($HIGHEST); refreshing its version tag but leaving :latest untouched" fi docker-rebuild: needs: config if: needs.config.outputs.has-secrets == '1' 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 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 }} BUILD_PRESET: ${{ matrix.build_preset }} LATEST_RELEASE: ${{ needs.config.outputs.latest-release }} FORCE_LATEST_FLAG: ${{ needs.config.outputs.force-latest == '1' && '--force-latest' || '' }} 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. # `--force-latest` is only passed when the config job confirmed the # fetched release is the newest one (see FORCE_LATEST_FLAG above). supersetbot docker \ --push \ --preset "$BUILD_PRESET" \ --context release \ --context-ref "$LATEST_RELEASE" \ $FORCE_LATEST_FLAG \ --platform "linux/arm64" \ --platform "linux/amd64" # The whole point of this cron is catching base-image CVEs, so a silent # failure is the expensive case — a red X in the Actions tab nobody is # watching on a Monday. File a tracked issue when any rebuild leg fails so # a missed security refresh surfaces instead of sitting unnoticed. notify-on-failure: needs: [config, docker-rebuild] if: failure() && needs.config.outputs.has-secrets == '1' runs-on: ubuntu-24.04 permissions: contents: read issues: write steps: - name: Open a tracking issue env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPOSITORY: ${{ github.repository }} LATEST_RELEASE: ${{ needs.config.outputs.latest-release }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | gh issue create \ --repo "$REPOSITORY" \ --title "Scheduled Docker image refresh failed for ${LATEST_RELEASE}" \ --label "infra:container" \ --label "bug" \ --body "The weekly Docker base-image refresh failed for release \`${LATEST_RELEASE}\`. Published images may be missing upstream base-layer security patches until this is resolved. Failed run: ${RUN_URL}"