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: build: runs-on: ubuntu-latest 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.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Install apko run: | curl -fsSL https://github.com/chainguard-dev/apko/releases/latest/download/apko_$(curl -fsSL https://api.github.com/repos/chainguard-dev/apko/releases/latest | jq -r .tag_name | sed 's/^v//')_linux_amd64.tar.gz \ | tar xz --strip-components=1 -C /usr/local/bin - 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: Determine tag suffix id: suffix run: | if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then echo "TAG_SUFFIX=-test" >> $GITHUB_ENV echo "Prerelease detected — tags will use -test suffix" else echo "TAG_SUFFIX=" >> $GITHUB_ENV fi - name: Discover versions and build all images run: | set -euo pipefail # Discover supported .NET versions RELEASES=$(curl -fsSL "$RELEASES_INDEX_URL") SUPPORTED=$(echo "$RELEASES" | jq -c '[.["releases-index"][] | select(.["support-phase"] == "active" or .["support-phase"] == "go-live")]') 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 "LTS major: $LTS_MAJOR" echo "STS major: $STS_MAJOR" # Build apko base images (shared across all .NET versions) for VARIANT in runtime runtime-invariant sdk; do for ARCH in x86_64 aarch64; do echo "::group::Building apko base: $VARIANT ($ARCH)" mkdir -p build-${ARCH}/${VARIANT} apko build --arch $ARCH \ apko/${VARIANT}.yaml ${VARIANT}:latest build-${ARCH}/${VARIANT}/rootfs.tar.gz echo "::endgroup::" done done # Iterate over each supported .NET version echo "$SUPPORTED" | jq -r '.[] | "\(.["channel-version"] | split(".")[0]) \(.["latest-runtime"]) \(.["latest-sdk"]) \(.["release-type"])"' | \ while read -r MAJOR RUNTIME SDK TYPE; do echo "=============================================" echo "Building .NET $MAJOR: runtime=$RUNTIME sdk=$SDK type=$TYPE" echo "=============================================" # Download .NET binaries 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 echo "::group::Downloading ASP.NET Core runtime $RUNTIME ($DOTNET_ARCH)" mkdir -p dotnet-${PLATFORM_ARCH}/runtime curl -fsSL "https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/${RUNTIME}/aspnetcore-runtime-${RUNTIME}-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::" echo "::group::Downloading .NET SDK $SDK ($DOTNET_ARCH)" mkdir -p dotnet-${PLATFORM_ARCH}/sdk curl -fsSL "https://dotnetcli.azureedge.net/dotnet/Sdk/${SDK}/dotnet-sdk-${SDK}-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 # Build and push each variant for VARIANT in runtime runtime-invariant sdk; do if [ "$VARIANT" = "sdk" ]; then VERSION="$SDK" DOTNET_DIR="sdk" else VERSION="$RUNTIME" DOTNET_DIR="runtime" fi echo "::group::Building $VARIANT-$MAJOR" # Prepare build context CONTEXT="context-${VARIANT}-${MAJOR}" rm -rf "$CONTEXT" mkdir -p "$CONTEXT" for ARCH in amd64 arm64; do APKO_ARCH=$([[ "$ARCH" == "amd64" ]] && echo "x86_64" || echo "aarch64") mkdir -p "$CONTEXT/build-${ARCH}/${VARIANT}" cp "build-${APKO_ARCH}/${VARIANT}/rootfs.tar.gz" "$CONTEXT/build-${ARCH}/${VARIANT}/" mkdir -p "$CONTEXT/dotnet-${ARCH}" cp -r "dotnet-${ARCH}/${DOTNET_DIR}" "$CONTEXT/dotnet-${ARCH}/" done cp "dockerfiles/${VARIANT}.Dockerfile" "$CONTEXT/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/" echo "::endgroup::" done # Docker Scout CVE scan for VARIANT in runtime runtime-invariant sdk; do echo "::group::Scout scan: ${VARIANT}-${MAJOR}" docker scout cves "$DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX}" --only-severity critical,high || true echo "::endgroup::" done # Clean up .NET binaries for this version before next iteration rm -rf dotnet-amd64 dotnet-arm64 done