name: Build and Push .NET Images on: release: types: [published, prereleased] workflow_dispatch: schedule: # Weekly rebuild for CVE patches (Sunday 6am UTC) - cron: "0 6 * * 0" permissions: id-token: write contents: read packages: write env: DOCKER_IMAGE: docker.io/svrnty/dotnet RELEASES_INDEX_URL: https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json jobs: discover: runs-on: ubuntu-latest outputs: matrix: ${{ steps.discover.outputs.matrix }} lts_major: ${{ steps.discover.outputs.lts_major }} sts_major: ${{ steps.discover.outputs.sts_major }} tag_suffix: ${{ steps.suffix.outputs.tag_suffix }} steps: - name: Determine tag suffix id: suffix run: | if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then echo "tag_suffix=-test" >> $GITHUB_OUTPUT echo "Prerelease detected — tags will use -test suffix" else echo "tag_suffix=" >> $GITHUB_OUTPUT fi - name: Discover supported .NET versions id: discover run: | set -euo pipefail RELEASES=$(curl -fsSL "$RELEASES_INDEX_URL") # Filter active/go-live versions (excludes EOL automatically) SUPPORTED=$(echo "$RELEASES" | jq -c '[.["releases-index"][] | select(.["support-phase"] == "active" or .["support-phase"] == "go-live")]') # Build matrix JSON MATRIX=$(echo "$SUPPORTED" | jq -c '{include: [.[] | { major: (.["channel-version"] | split(".")[0]), runtime: .["latest-runtime"], sdk: .["latest-sdk"], type: .["release-type"] }]}') # Find highest LTS and STS majors LTS_MAJOR=$(echo "$SUPPORTED" | jq -r '[.[] | select(.["release-type"] == "lts")] | sort_by(.["channel-version"] | split(".") | map(tonumber)) | last | .["channel-version"] | split(".")[0]') STS_MAJOR=$(echo "$SUPPORTED" | jq -r '[.[] | select(.["release-type"] == "sts")] | sort_by(.["channel-version"] | split(".") | map(tonumber)) | last | .["channel-version"] | split(".")[0] // ""') echo "matrix=$MATRIX" >> $GITHUB_OUTPUT echo "lts_major=$LTS_MAJOR" >> $GITHUB_OUTPUT echo "sts_major=$STS_MAJOR" >> $GITHUB_OUTPUT echo "Matrix: $MATRIX" echo "LTS major: $LTS_MAJOR" echo "STS major: $STS_MAJOR" build: runs-on: ubuntu-latest needs: discover strategy: fail-fast: false matrix: ${{ fromJson(needs.discover.outputs.matrix) }} env: LTS_MAJOR: ${{ needs.discover.outputs.lts_major }} STS_MAJOR: ${{ needs.discover.outputs.sts_major }} TAG_SUFFIX: ${{ needs.discover.outputs.tag_suffix }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to DockerHub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_SVRNTY_USERNAME }} password: ${{ secrets.DOCKERHUB_SVRNTY_ACCESS_TOKEN }} - name: Build apko base images run: | set -euo pipefail for VARIANT in runtime runtime-invariant sdk; do for ARCH in x86_64 aarch64; do echo "::group::Building apko base: $VARIANT ($ARCH)" mkdir -p ${{ github.workspace }}/build-${ARCH}/${VARIANT} docker run --rm \ -v ${{ github.workspace }}/apko:/work:ro \ -v ${{ github.workspace }}/build-${ARCH}/${VARIANT}:/output \ cgr.dev/chainguard/apko build \ --arch $ARCH /work/${VARIANT}.yaml ${VARIANT}:latest /output/rootfs.tar.gz echo "::endgroup::" done done - name: Download .NET binaries run: | set -euo pipefail RUNTIME_VERSION="${{ matrix.runtime }}" SDK_VERSION="${{ matrix.sdk }}" for ARCH in x86_64 aarch64; do if [ "$ARCH" = "x86_64" ]; then DOTNET_ARCH="x64" PLATFORM_ARCH="amd64" else DOTNET_ARCH="arm64" PLATFORM_ARCH="arm64" fi # Download ASP.NET Core runtime (for runtime and runtime-invariant) echo "::group::Downloading ASP.NET Core runtime $RUNTIME_VERSION ($DOTNET_ARCH)" mkdir -p dotnet-${PLATFORM_ARCH}/runtime curl -fsSL "https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/${RUNTIME_VERSION}/aspnetcore-runtime-${RUNTIME_VERSION}-linux-${DOTNET_ARCH}.tar.gz" \ -o /tmp/aspnet-runtime-${PLATFORM_ARCH}.tar.gz tar xf /tmp/aspnet-runtime-${PLATFORM_ARCH}.tar.gz -C dotnet-${PLATFORM_ARCH}/runtime echo "::endgroup::" # Download .NET SDK echo "::group::Downloading .NET SDK $SDK_VERSION ($DOTNET_ARCH)" mkdir -p dotnet-${PLATFORM_ARCH}/sdk curl -fsSL "https://dotnetcli.azureedge.net/dotnet/Sdk/${SDK_VERSION}/dotnet-sdk-${SDK_VERSION}-linux-${DOTNET_ARCH}.tar.gz" \ -o /tmp/dotnet-sdk-${PLATFORM_ARCH}.tar.gz tar xf /tmp/dotnet-sdk-${PLATFORM_ARCH}.tar.gz -C dotnet-${PLATFORM_ARCH}/sdk echo "::endgroup::" done - name: Build and push runtime image run: | set -euo pipefail MAJOR="${{ matrix.major }}" VERSION="${{ matrix.runtime }}" VARIANT="runtime" # Prepare build context mkdir -p context-runtime for ARCH in amd64 arm64; do APKO_ARCH=$([[ "$ARCH" == "amd64" ]] && echo "x86_64" || echo "aarch64") mkdir -p context-runtime/build-${ARCH}/runtime cp build-${APKO_ARCH}/${VARIANT}/rootfs.tar.gz context-runtime/build-${ARCH}/runtime/ mkdir -p context-runtime/dotnet-${ARCH} cp -r dotnet-${ARCH}/runtime context-runtime/dotnet-${ARCH}/ done cp dockerfiles/runtime.Dockerfile context-runtime/Dockerfile # Determine tags TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}" if [ "$MAJOR" = "$LTS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}" fi if [ "$MAJOR" = "$STS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}" fi echo "Building $VARIANT with tags: $TAGS" docker buildx build --platform linux/amd64,linux/arm64 --push \ --sbom=true --provenance=mode=max \ $TAGS \ context-runtime/ - name: Build and push runtime-invariant image run: | set -euo pipefail MAJOR="${{ matrix.major }}" VERSION="${{ matrix.runtime }}" VARIANT="runtime-invariant" mkdir -p context-runtime-invariant for ARCH in amd64 arm64; do APKO_ARCH=$([[ "$ARCH" == "amd64" ]] && echo "x86_64" || echo "aarch64") mkdir -p context-runtime-invariant/build-${ARCH}/runtime-invariant cp build-${APKO_ARCH}/${VARIANT}/rootfs.tar.gz context-runtime-invariant/build-${ARCH}/runtime-invariant/ mkdir -p context-runtime-invariant/dotnet-${ARCH} cp -r dotnet-${ARCH}/runtime context-runtime-invariant/dotnet-${ARCH}/ done cp dockerfiles/runtime-invariant.Dockerfile context-runtime-invariant/Dockerfile TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}" if [ "$MAJOR" = "$LTS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}" fi if [ "$MAJOR" = "$STS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}" fi echo "Building $VARIANT with tags: $TAGS" docker buildx build --platform linux/amd64,linux/arm64 --push \ --sbom=true --provenance=mode=max \ $TAGS \ context-runtime-invariant/ - name: Build and push SDK image run: | set -euo pipefail MAJOR="${{ matrix.major }}" VERSION="${{ matrix.sdk }}" VARIANT="sdk" mkdir -p context-sdk for ARCH in amd64 arm64; do APKO_ARCH=$([[ "$ARCH" == "amd64" ]] && echo "x86_64" || echo "aarch64") mkdir -p context-sdk/build-${ARCH}/sdk cp build-${APKO_ARCH}/${VARIANT}/rootfs.tar.gz context-sdk/build-${ARCH}/sdk/ mkdir -p context-sdk/dotnet-${ARCH} cp -r dotnet-${ARCH}/sdk context-sdk/dotnet-${ARCH}/ done cp dockerfiles/sdk.Dockerfile context-sdk/Dockerfile TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}" if [ "$MAJOR" = "$LTS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}" fi if [ "$MAJOR" = "$STS_MAJOR" ]; then TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}" fi echo "Building $VARIANT with tags: $TAGS" docker buildx build --platform linux/amd64,linux/arm64 --push \ --sbom=true --provenance=mode=max \ $TAGS \ context-sdk/ - name: Install Docker Scout run: | curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh -o install-scout.sh sh install-scout.sh - name: Docker Scout CVE Scan run: | MAJOR="${{ matrix.major }}" for VARIANT in runtime runtime-invariant sdk; do echo "::group::Scout scan: ${VARIANT}-${MAJOR}" docker scout cves ${{ env.DOCKER_IMAGE }}:${VARIANT}-${MAJOR}${TAG_SUFFIX} --only-severity critical,high || true echo "::endgroup::" done