From 2882a136bf6cffae898d9330ff14bf3c50c99dfb Mon Sep 17 00:00:00 2001 From: Kangyan-Zhou Date: Thu, 23 Apr 2026 14:06:01 -0700 Subject: [PATCH] [CI] Consolidate Docker release workflows into reusable workflow (#22541) Co-authored-by: Claude Opus 4.6 (1M context) --- .../workflows/_docker-build-and-publish.yml | 283 +++++++++++++++++ .github/workflows/_docker-cleanup-nightly.yml | 78 +++++ .github/workflows/release-docker-dev.yml | 224 +++---------- .github/workflows/release-docker-runtime.yml | 294 ++--------------- .github/workflows/release-docker.yml | 295 ++---------------- 5 files changed, 460 insertions(+), 714 deletions(-) create mode 100644 .github/workflows/_docker-build-and-publish.yml create mode 100644 .github/workflows/_docker-cleanup-nightly.yml diff --git a/.github/workflows/_docker-build-and-publish.yml b/.github/workflows/_docker-build-and-publish.yml new file mode 100644 index 000000000..d2b8246fe --- /dev/null +++ b/.github/workflows/_docker-build-and-publish.yml @@ -0,0 +1,283 @@ +name: Build and Publish Multi-Arch Docker Images + +# Reusable workflow: builds CUDA 12 + CUDA 13 images for amd64 and arm64, +# then creates multi-arch manifests with caller-specified tags. + +on: + workflow_call: + inputs: + docker_target: + description: "Dockerfile target stage (framework or runtime)" + required: true + type: string + sgl_version: + description: "Version string passed as SGL_VERSION build arg (empty to skip)" + required: false + type: string + default: "" + extra_build_args: + description: "Additional --build-arg flags appended to docker buildx build" + required: false + type: string + default: "" + checkout_ref: + description: "Git ref to checkout (empty for default)" + required: false + type: string + default: "" + tag_config: + description: 'JSON array of {"cuda":"cu129|cu130","tags":["tag1","tag2"]}. Tags support {version} substitution.' + required: true + type: string + use_environment: + description: "GitHub environment name (e.g. prod) or empty for none" + required: false + type: string + default: "" + image_repo: + description: "Docker Hub repo to push to (e.g. lmsysorg/sglang-staging for testing)" + required: false + type: string + default: "lmsysorg/sglang" + +jobs: + build-x86: + if: github.repository == 'sgl-project/sglang' + environment: ${{ inputs.use_environment || null }} + runs-on: x64-docker-build-node + outputs: + digest-cu129: ${{ steps.build-cu129.outputs.digest }} + digest-cu130: ${{ steps.build-cu130.outputs.digest }} + steps: + - name: Delete huge unnecessary tools folder + run: rm -rf /opt/hostedtoolcache + + - name: Cleanup workspace (remove root-owned files from prior runs) + run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true + + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkout_ref || github.ref }} + + - name: Free disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + docker-images: true + android: true + dotnet: true + haskell: true + large-packages: true + swap-storage: true + + - name: Prune Docker to reclaim disk space + run: | + docker buildx prune --filter "until=72h" -f + docker system prune -af --filter "until=72h" + docker volume prune -af + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push AMD64 image (CUDA 12) + id: build-cu129 + run: | + VERSION_ARG="" + if [ -n "${{ inputs.sgl_version }}" ]; then + VERSION_ARG="--build-arg SGL_VERSION=${{ inputs.sgl_version }}" + fi + + docker buildx build \ + --target ${{ inputs.docker_target }} \ + --platform linux/amd64 \ + --output type=image,name=${{ inputs.image_repo }},push-by-digest=true,name-canonical=true,push=true \ + -f docker/Dockerfile \ + --build-arg CUDA_VERSION=12.9.1 \ + --build-arg BUILD_TYPE=all \ + --build-arg GRACE_BLACKWELL=0 \ + --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ + ${VERSION_ARG} \ + ${{ inputs.extra_build_args }} \ + --metadata-file /tmp/metadata-cu129.json \ + --no-cache \ + . + + DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129.json'))['containerimage.digest'])") + echo "Pushed digest: ${DIGEST}" + echo "digest=${DIGEST}" >> $GITHUB_OUTPUT + + - name: Build and push AMD64 image (CUDA 13) + id: build-cu130 + run: | + VERSION_ARG="" + if [ -n "${{ inputs.sgl_version }}" ]; then + VERSION_ARG="--build-arg SGL_VERSION=${{ inputs.sgl_version }}" + fi + + docker buildx build \ + --target ${{ inputs.docker_target }} \ + --platform linux/amd64 \ + --output type=image,name=${{ inputs.image_repo }},push-by-digest=true,name-canonical=true,push=true \ + -f docker/Dockerfile \ + --build-arg CUDA_VERSION=13.0.1 \ + --build-arg BUILD_TYPE=all \ + --build-arg GRACE_BLACKWELL=0 \ + --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ + ${VERSION_ARG} \ + ${{ inputs.extra_build_args }} \ + --metadata-file /tmp/metadata-cu130.json \ + --no-cache \ + . + + DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130.json'))['containerimage.digest'])") + echo "Pushed digest: ${DIGEST}" + echo "digest=${DIGEST}" >> $GITHUB_OUTPUT + + build-arm64: + if: github.repository == 'sgl-project/sglang' + environment: ${{ inputs.use_environment || null }} + runs-on: arm-docker-build-node + outputs: + digest-cu129: ${{ steps.build-cu129.outputs.digest }} + digest-cu130: ${{ steps.build-cu130.outputs.digest }} + steps: + - name: Delete huge unnecessary tools folder + run: rm -rf /opt/hostedtoolcache + + - name: Cleanup workspace (remove root-owned files from prior runs) + run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true + + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkout_ref || github.ref }} + + - name: Prune Docker to reclaim disk space + run: | + docker buildx prune --filter "until=72h" -f + docker system prune -af --filter "until=72h" + docker volume prune -af + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push ARM64 image (CUDA 12) + id: build-cu129 + run: | + VERSION_ARG="" + if [ -n "${{ inputs.sgl_version }}" ]; then + VERSION_ARG="--build-arg SGL_VERSION=${{ inputs.sgl_version }}" + fi + + docker buildx build \ + --target ${{ inputs.docker_target }} \ + --platform linux/arm64 \ + --output type=image,name=${{ inputs.image_repo }},push-by-digest=true,name-canonical=true,push=true \ + -f docker/Dockerfile \ + --build-arg CUDA_VERSION=12.9.1 \ + --build-arg BUILD_TYPE=all \ + --build-arg GRACE_BLACKWELL=1 \ + --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ + ${VERSION_ARG} \ + ${{ inputs.extra_build_args }} \ + --metadata-file /tmp/metadata-cu129.json \ + --no-cache \ + . + + DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129.json'))['containerimage.digest'])") + echo "Pushed digest: ${DIGEST}" + echo "digest=${DIGEST}" >> $GITHUB_OUTPUT + + - name: Build and push ARM64 image (CUDA 13) + id: build-cu130 + run: | + VERSION_ARG="" + if [ -n "${{ inputs.sgl_version }}" ]; then + VERSION_ARG="--build-arg SGL_VERSION=${{ inputs.sgl_version }}" + fi + + docker buildx build \ + --target ${{ inputs.docker_target }} \ + --platform linux/arm64 \ + --output type=image,name=${{ inputs.image_repo }},push-by-digest=true,name-canonical=true,push=true \ + -f docker/Dockerfile \ + --build-arg CUDA_VERSION=13.0.1 \ + --build-arg BUILD_TYPE=all \ + --build-arg GRACE_BLACKWELL=1 \ + --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ + ${VERSION_ARG} \ + ${{ inputs.extra_build_args }} \ + --metadata-file /tmp/metadata-cu130.json \ + --no-cache \ + . + + DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130.json'))['containerimage.digest'])") + echo "Pushed digest: ${DIGEST}" + echo "digest=${DIGEST}" >> $GITHUB_OUTPUT + + create-manifests: + runs-on: ubuntu-latest + needs: [build-x86, build-arm64] + if: github.repository == 'sgl-project/sglang' + environment: ${{ inputs.use_environment || null }} + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create multi-arch manifests + env: + TAG_CONFIG: ${{ inputs.tag_config }} + SGL_VERSION: ${{ inputs.sgl_version }} + IMAGE_REPO: ${{ inputs.image_repo }} + X86_CU129: ${{ needs.build-x86.outputs.digest-cu129 }} + X86_CU130: ${{ needs.build-x86.outputs.digest-cu130 }} + ARM64_CU129: ${{ needs.build-arm64.outputs.digest-cu129 }} + ARM64_CU130: ${{ needs.build-arm64.outputs.digest-cu130 }} + SHORT_SHA: ${{ github.sha }} + run: | + echo "${TAG_CONFIG}" | jq -c '.[]' | while read -r entry; do + CUDA=$(echo "${entry}" | jq -r '.cuda') + + if [ "${CUDA}" = "cu129" ]; then + X86_DIGEST="${X86_CU129}" + ARM64_DIGEST="${ARM64_CU129}" + else + X86_DIGEST="${X86_CU130}" + ARM64_DIGEST="${ARM64_CU130}" + fi + + TAG_ARGS="" + for tag in $(echo "${entry}" | jq -r '.tags[]'); do + # Substitute template variables + tag=$(echo "${tag}" | sed "s/{version}/${SGL_VERSION}/g") + tag=$(echo "${tag}" | sed "s/{date}/$(date +%Y%m%d)/g") + tag=$(echo "${tag}" | sed "s/{short_sha}/${SHORT_SHA:0:8}/g") + TAG_ARGS="${TAG_ARGS} -t ${IMAGE_REPO}:${tag}" + done + + docker buildx imagetools create \ + ${TAG_ARGS} \ + ${IMAGE_REPO}@${X86_DIGEST} \ + ${IMAGE_REPO}@${ARM64_DIGEST} + + echo "Published:${TAG_ARGS}" + done diff --git a/.github/workflows/_docker-cleanup-nightly.yml b/.github/workflows/_docker-cleanup-nightly.yml new file mode 100644 index 000000000..fda12e4e0 --- /dev/null +++ b/.github/workflows/_docker-cleanup-nightly.yml @@ -0,0 +1,78 @@ +name: Cleanup Old Nightly Docker Tags + +# Reusable workflow: deletes old nightly Docker Hub tags, keeping the most recent ones. +# Can also be triggered manually to clean up tags on demand. + +on: + workflow_call: + inputs: + tag_prefixes: + description: 'JSON array of tag prefixes to clean up (e.g. ["nightly-dev", "nightly-dev-cu13"])' + required: true + type: string + keep_count: + description: "Number of most recent tags to keep per prefix" + required: false + type: number + default: 14 + image_repo: + description: "Docker Hub repo to clean up (e.g. lmsysorg/sglang-staging for testing)" + required: false + type: string + default: "lmsysorg/sglang" + workflow_dispatch: + inputs: + tag_prefixes: + description: 'JSON array of tag prefixes to clean up (e.g. ["nightly-dev", "nightly-dev-cu13"])' + required: true + type: string + keep_count: + description: "Number of most recent tags to keep per prefix" + required: false + type: number + default: 14 + image_repo: + description: "Docker Hub repo to clean up (e.g. lmsysorg/sglang-staging for testing)" + required: false + type: string + default: "lmsysorg/sglang" + +jobs: + cleanup: + if: github.repository == 'sgl-project/sglang' + runs-on: ubuntu-latest + steps: + - name: Cleanup old nightly tags + env: + TAG_PREFIXES: ${{ inputs.tag_prefixes }} + KEEP_COUNT: ${{ inputs.keep_count }} + IMAGE_REPO: ${{ inputs.image_repo }} + run: | + TOKEN=$(curl -s -H "Content-Type: application/json" \ + -X POST -d '{"username": "${{ secrets.DOCKERHUB_USERNAME }}", "password": "${{ secrets.DOCKERHUB_TOKEN }}"}' \ + https://hub.docker.com/v2/users/login/ | jq -r .token) + + TAGS_RESPONSE=$(curl -s -H "Authorization: JWT $TOKEN" \ + "https://hub.docker.com/v2/repositories/${IMAGE_REPO}/tags/?page_size=100") + + echo "${TAG_PREFIXES}" | jq -r '.[]' | while read -r PREFIX; do + echo "--- Checking prefix: ${PREFIX} ---" + + TAGS=$(echo "$TAGS_RESPONSE" | jq -r \ + --arg prefix "${PREFIX}" \ + '.results[] | select(.name | test("^\($prefix)-[0-9]")) | "\(.last_updated)|\(.name)"' \ + | sort -r | cut -d'|' -f2) + + TAG_COUNT=$(echo "$TAGS" | grep -c . || true) + if [ "$TAG_COUNT" -gt "$KEEP_COUNT" ]; then + echo "Found $TAG_COUNT tags, keeping only the $KEEP_COUNT most recent" + TAGS_TO_DELETE=$(echo "$TAGS" | tail -n +$((KEEP_COUNT + 1))) + for tag in $TAGS_TO_DELETE; do + echo "Deleting tag: $tag" + curl -X DELETE -H "Authorization: JWT $TOKEN" \ + "https://hub.docker.com/v2/repositories/${IMAGE_REPO}/tags/$tag/" + done + else + echo "Only $TAG_COUNT tags found, no cleanup needed" + fi + done diff --git a/.github/workflows/release-docker-dev.yml b/.github/workflows/release-docker-dev.yml index b87bd382d..a41c57c07 100644 --- a/.github/workflows/release-docker-dev.yml +++ b/.github/workflows/release-docker-dev.yml @@ -11,6 +11,10 @@ on: description: "Custom tag suffix (overrides pr_number in tag). E.g. 'my-test' → dev-my-test, dev-cu13-my-test, etc." required: false default: "" + image_repo: + description: "Docker Hub repo to push to. Use lmsysorg/sglang-staging for testing." + required: false + default: "lmsysorg/sglang" schedule: - cron: "0 0 * * *" @@ -19,147 +23,32 @@ concurrency: cancel-in-progress: true jobs: - build-dev: - if: ${{ github.repository == 'sgl-project/sglang' }} - runs-on: ${{ matrix.runner }} - strategy: - matrix: - include: - - runner: x64-docker-build-node - platform: linux/amd64 - build_type: all - grace_blackwell: 0 - arch_tag: x86 - version: 12.9.1 - - runner: arm-docker-build-node - platform: linux/arm64 - build_type: all - grace_blackwell: 1 - arch_tag: arm64 - version: 12.9.1 - - runner: x64-docker-build-node - platform: linux/amd64 - build_type: all - grace_blackwell: 0 - arch_tag: x86-cu13 - version: 13.0.1 - - runner: arm-docker-build-node - platform: linux/arm64 - build_type: all - grace_blackwell: 1 - arch_tag: arm64-cu13 - version: 13.0.1 + prepare: + if: github.repository == 'sgl-project/sglang' + runs-on: ubuntu-latest + outputs: + checkout_ref: ${{ steps.config.outputs.checkout_ref }} + extra_build_args: ${{ steps.config.outputs.extra_build_args }} + tag_config: ${{ steps.config.outputs.tag_config }} steps: - - name: Delete huge unnecessary tools folder - run: rm -rf /opt/hostedtoolcache - - - name: Cleanup workspace (remove root-owned files from prior runs) - run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true - - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }} - - - name: Free disk space - uses: jlumbroso/free-disk-space@main - with: - tool-cache: true - docker-images: true - android: true - dotnet: true - haskell: true - large-packages: true - swap-storage: true - - - name: Prune Docker to reclaim disk space + - name: Compute build configuration + id: config run: | - docker buildx prune --filter "until=72h" -f - docker system prune -af --filter "until=72h" - docker volume prune -af - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and Push Dev Image - run: | - # Nightly (schedule) installs latest release; manual dispatch builds from checked-out source - if [ "${{ github.event_name }}" = "schedule" ]; then - SOURCE_ARG="--build-arg USE_LATEST_SGLANG=1" + # Determine checkout ref + if [ -n "${{ inputs.pr_number }}" ]; then + echo "checkout_ref=refs/pull/${{ inputs.pr_number }}/head" >> $GITHUB_OUTPUT else - SOURCE_ARG="--build-arg BRANCH_TYPE=local" + echo "checkout_ref=" >> $GITHUB_OUTPUT fi - docker buildx build \ - --platform ${{ matrix.platform }} \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - --target framework_final \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=${{ matrix.version }} \ - --build-arg BUILD_TYPE=${{ matrix.build_type }} \ - --build-arg CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) \ - --build-arg GRACE_BLACKWELL=${{ matrix.grace_blackwell }} \ - ${SOURCE_ARG} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --metadata-file /tmp/metadata.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "${DIGEST}" > /tmp/digest.txt - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digest-${{ matrix.arch_tag }} - path: /tmp/digest.txt - retention-days: 1 - - create-manifests: - runs-on: ubuntu-22.04 - needs: [build-dev] - if: ${{ github.repository == 'sgl-project/sglang' }} - strategy: - matrix: - variant: - - base: dev - x86: x86 - arm64: arm64 - - base: dev-cu13 - x86: x86-cu13 - arm64: arm64-cu13 - steps: - - uses: docker/setup-buildx-action@v3 - - - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Download x86 digest - uses: actions/download-artifact@v4 - with: - name: digest-${{ matrix.variant.x86 }} - path: /tmp/digests/x86 - - - name: Download arm64 digest - uses: actions/download-artifact@v4 - with: - name: digest-${{ matrix.variant.arm64 }} - path: /tmp/digests/arm64 - - - name: Create multi-arch manifest - run: | - X86_DIGEST=$(cat /tmp/digests/x86/digest.txt) - ARM64_DIGEST=$(cat /tmp/digests/arm64/digest.txt) + # Determine extra build args + if [ "${{ github.event_name }}" = "schedule" ]; then + echo "extra_build_args=--build-arg USE_LATEST_SGLANG=1 --build-arg CMAKE_BUILD_PARALLEL_LEVEL=\$(nproc)" >> $GITHUB_OUTPUT + else + echo "extra_build_args=--build-arg BRANCH_TYPE=local --build-arg CMAKE_BUILD_PARALLEL_LEVEL=\$(nproc)" >> $GITHUB_OUTPUT + fi + # Determine tag suffix SUFFIX="" if [ -n "${{ inputs.tag }}" ]; then SUFFIX="-${{ inputs.tag }}" @@ -167,46 +56,31 @@ jobs: SUFFIX="-pr-${{ inputs.pr_number }}" fi - TAG="${{ matrix.variant.base }}${SUFFIX}" - - # For nightly (no suffix), also stamp a dated tag - EXTRA_TAG="" + # Build tag config if [ -z "${SUFFIX}" ]; then - SHORT_SHA="${{ github.sha }}" - EXTRA_TAG="-t lmsysorg/sglang:nightly-${TAG}-$(date +%Y%m%d)-${SHORT_SHA:0:8}" - fi - - docker buildx imagetools create \ - -t lmsysorg/sglang:${TAG} \ - ${EXTRA_TAG} \ - lmsysorg/sglang@${X86_DIGEST} \ - lmsysorg/sglang@${ARM64_DIGEST} - - echo "✓ Published lmsysorg/sglang:${TAG}" - - - name: Cleanup Old Nightly Builds - if: ${{ !inputs.tag && !inputs.pr_number }} - run: | - TOKEN=$(curl -s -H "Content-Type: application/json" \ - -X POST -d '{"username": "${{ secrets.DOCKERHUB_USERNAME }}", "password": "${{ secrets.DOCKERHUB_TOKEN }}"}' \ - https://hub.docker.com/v2/users/login/ | jq -r .token) - - TAGS_RESPONSE=$(curl -s -H "Authorization: JWT $TOKEN" \ - "https://hub.docker.com/v2/repositories/lmsysorg/sglang/tags/?page_size=100") - - TAGS=$(echo "$TAGS_RESPONSE" | jq -r \ - '.results[] | select(.name | test("^nightly-${{ matrix.variant.base }}-[0-9]")) | "\(.last_updated)|\(.name)"' \ - | sort -r | cut -d'|' -f2) - - TAG_COUNT=$(echo "$TAGS" | wc -l) - if [ "$TAG_COUNT" -gt 14 ]; then - echo "Found $TAG_COUNT nightly builds, keeping only the 14 most recent" - TAGS_TO_DELETE=$(echo "$TAGS" | tail -n +15) - for tag in $TAGS_TO_DELETE; do - echo "Deleting tag: $tag" - curl -X DELETE -H "Authorization: JWT $TOKEN" \ - "https://hub.docker.com/v2/repositories/lmsysorg/sglang/tags/$tag/" - done + # Nightly: include dated tags + TAG_CONFIG='[{"cuda":"cu129","tags":["dev","nightly-dev-{date}-{short_sha}"]},{"cuda":"cu130","tags":["dev-cu13","nightly-dev-cu13-{date}-{short_sha}"]}]' else - echo "Only $TAG_COUNT nightly builds found, no cleanup needed" + TAG_CONFIG="[{\"cuda\":\"cu129\",\"tags\":[\"dev${SUFFIX}\"]},{\"cuda\":\"cu130\",\"tags\":[\"dev-cu13${SUFFIX}\"]}]" fi + echo "tag_config=${TAG_CONFIG}" >> $GITHUB_OUTPUT + + build-and-publish: + needs: prepare + uses: ./.github/workflows/_docker-build-and-publish.yml + with: + docker_target: framework_final + checkout_ref: ${{ needs.prepare.outputs.checkout_ref }} + extra_build_args: ${{ needs.prepare.outputs.extra_build_args }} + tag_config: ${{ needs.prepare.outputs.tag_config }} + image_repo: ${{ inputs.image_repo || 'lmsysorg/sglang' }} + secrets: inherit + + cleanup-nightly: + needs: build-and-publish + if: ${{ !inputs.tag && !inputs.pr_number }} + uses: ./.github/workflows/_docker-cleanup-nightly.yml + with: + tag_prefixes: '["nightly-dev", "nightly-dev-cu13"]' + image_repo: ${{ inputs.image_repo || 'lmsysorg/sglang' }} + secrets: inherit diff --git a/.github/workflows/release-docker-runtime.yml b/.github/workflows/release-docker-runtime.yml index c6013f6e0..b2002a627 100644 --- a/.github/workflows/release-docker-runtime.yml +++ b/.github/workflows/release-docker-runtime.yml @@ -1,6 +1,6 @@ name: Release Docker Runtime Images # -# This workflow builds and publishes runtime Docker images (production-optimized, ~50% smaller): +# Builds and publishes runtime Docker images (production-optimized, ~50% smaller): # - lmsysorg/sglang:v{version}-runtime, lmsysorg/sglang:latest-runtime # - lmsysorg/sglang:v{version}-cu130-runtime, lmsysorg/sglang:latest-cu130-runtime # @@ -13,287 +13,43 @@ on: version: description: "Version to build (without v prefix, e.g., 0.5.7)" required: true + image_repo: + description: "Docker Hub repo to push to. Use lmsysorg/sglang-staging for testing." + required: false + default: "lmsysorg/sglang" jobs: - publish-x86: + resolve-version: if: github.repository == 'sgl-project/sglang' - environment: "prod" + runs-on: ubuntu-latest outputs: - digest-cu129: ${{ steps.build-cu129.outputs.digest }} - digest-cu130: ${{ steps.build-cu130.outputs.digest }} - strategy: - matrix: - variant: - - cuda_version: "12.9.1" - build_type: "all" - grace_blackwell: 0 - runs-on: x64-docker-build-node + version: ${{ steps.version.outputs.version }} steps: - - name: Delete huge unnecessary tools folder - run: rm -rf /opt/hostedtoolcache - - - name: Cleanup workspace (remove root-owned files from prior runs) - run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Free disk space - uses: jlumbroso/free-disk-space@main - with: - tool-cache: false - docker-images: false - android: true - dotnet: true - haskell: true - large-packages: true - swap-storage: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag + - name: Get version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ github.event.inputs.version }}" else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) VERSION="${GITHUB_REF_NAME#v}" fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" + if [ -z "$VERSION" ] || ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "::error::Invalid version: $VERSION (expected: X.Y.Z)" exit 1 fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - name: Build and Push AMD64 Runtime - id: build-cu129 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target runtime \ - --platform linux/amd64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=${{ matrix.variant.cuda_version }} \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg GRACE_BLACKWELL=${{ matrix.variant.grace_blackwell }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu129-runtime.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129-runtime.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - - name: Build and Push AMD64 Runtime (CUDA 13) - id: build-cu130 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target runtime \ - --platform linux/amd64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=13.0.1 \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg GRACE_BLACKWELL=0 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu130-runtime.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130-runtime.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - publish-arm64: - if: github.repository == 'sgl-project/sglang' - environment: "prod" - outputs: - digest-cu129: ${{ steps.build-cu129.outputs.digest }} - digest-cu130: ${{ steps.build-cu130.outputs.digest }} - strategy: - matrix: - variant: - - cuda_version: "12.9.1" - build_type: "all" - grace_blackwell: 1 - runs-on: arm-docker-build-node - steps: - - name: Delete huge unnecessary tools folder - run: rm -rf /opt/hostedtoolcache - - - name: Cleanup workspace (remove root-owned files from prior runs) - run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) - VERSION="${GITHUB_REF_NAME#v}" - fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" - exit 1 - fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - - name: Build and Push ARM64 Runtime - id: build-cu129 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target runtime \ - --platform linux/arm64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=${{ matrix.variant.cuda_version }} \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg GRACE_BLACKWELL=${{ matrix.variant.grace_blackwell }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu129-runtime.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129-runtime.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - - name: Build and Push ARM64 Runtime (CUDA 13) - id: build-cu130 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target runtime \ - --platform linux/arm64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=13.0.1 \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg GRACE_BLACKWELL=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu130-runtime.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130-runtime.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - create-manifests: - runs-on: ubuntu-22.04 - needs: [publish-x86, publish-arm64] - if: github.repository == 'sgl-project/sglang' - environment: "prod" - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) - VERSION="${GITHUB_REF_NAME#v}" - fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" - exit 1 - fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - - name: Create multi-arch manifests - run: | - version=${{ steps.version.outputs.version }} - - CU129_AMD64_RT=${{ needs.publish-x86.outputs.digest-cu129 }} - CU130_AMD64_RT=${{ needs.publish-x86.outputs.digest-cu130 }} - CU129_ARM64_RT=${{ needs.publish-arm64.outputs.digest-cu129 }} - CU130_ARM64_RT=${{ needs.publish-arm64.outputs.digest-cu130 }} - - # Create versioned runtime manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:v${version}-runtime \ - lmsysorg/sglang@${CU129_AMD64_RT} \ - lmsysorg/sglang@${CU129_ARM64_RT} - - # Create latest runtime manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:latest-runtime \ - lmsysorg/sglang@${CU129_AMD64_RT} \ - lmsysorg/sglang@${CU129_ARM64_RT} - - # Create versioned CUDA 13 runtime manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:v${version}-cu130-runtime \ - lmsysorg/sglang@${CU130_AMD64_RT} \ - lmsysorg/sglang@${CU130_ARM64_RT} - - # Create latest CUDA 13 runtime manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:latest-cu130-runtime \ - lmsysorg/sglang@${CU130_AMD64_RT} \ - lmsysorg/sglang@${CU130_ARM64_RT} + build-and-publish: + needs: resolve-version + uses: ./.github/workflows/_docker-build-and-publish.yml + with: + docker_target: runtime + sgl_version: ${{ needs.resolve-version.outputs.version }} + use_environment: prod + image_repo: ${{ inputs.image_repo || 'lmsysorg/sglang' }} + tag_config: | + [ + {"cuda": "cu129", "tags": ["v${{ needs.resolve-version.outputs.version }}-runtime", "latest-runtime"]}, + {"cuda": "cu130", "tags": ["v${{ needs.resolve-version.outputs.version }}-cu130-runtime", "latest-cu130-runtime"]} + ] + secrets: inherit diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index e4089356f..71c2644ec 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -1,6 +1,6 @@ name: Release Docker Images # -# This workflow builds and publishes framework Docker images (full development environment): +# Builds and publishes framework Docker images (full development environment): # - lmsysorg/sglang:v{version}, lmsysorg/sglang:latest # - lmsysorg/sglang:v{version}-cu130, lmsysorg/sglang:latest-cu130 # @@ -13,288 +13,43 @@ on: version: description: "Version to build (without v prefix, e.g., 0.5.7)" required: true + image_repo: + description: "Docker Hub repo to push to. Use lmsysorg/sglang-staging for testing." + required: false + default: "lmsysorg/sglang" jobs: - publish-x86: + resolve-version: if: github.repository == 'sgl-project/sglang' - environment: "prod" + runs-on: ubuntu-latest outputs: - digest-cu129: ${{ steps.build-cu129.outputs.digest }} - digest-cu130: ${{ steps.build-cu130.outputs.digest }} - strategy: - matrix: - variant: - - cuda_version: "12.9.1" - build_type: "all" - grace_blackwell: 0 - runs-on: x64-docker-build-node + version: ${{ steps.version.outputs.version }} steps: - - name: Delete huge unnecessary tools folder - run: rm -rf /opt/hostedtoolcache - - - name: Cleanup workspace (remove root-owned files from prior runs) - run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Free disk space - uses: jlumbroso/free-disk-space@main - with: - tool-cache: false - docker-images: false - android: true - dotnet: true - haskell: true - large-packages: true - swap-storage: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag + - name: Get version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ github.event.inputs.version }}" else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) VERSION="${GITHUB_REF_NAME#v}" fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" + if [ -z "$VERSION" ] || ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "::error::Invalid version: $VERSION (expected: X.Y.Z)" exit 1 fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - name: Build AMD64 Framework - id: build-cu129 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target framework_final \ - --platform linux/amd64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=${{ matrix.variant.cuda_version }} \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg GRACE_BLACKWELL=${{ matrix.variant.grace_blackwell }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu129-framework.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129-framework.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - - name: Build and Push AMD64 Framework (CUDA 13) - id: build-cu130 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target framework_final \ - --platform linux/amd64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=13.0.1 \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg GRACE_BLACKWELL=0 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu130-framework.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130-framework.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - publish-arm64: - if: github.repository == 'sgl-project/sglang' - environment: "prod" - outputs: - digest-cu129: ${{ steps.build-cu129.outputs.digest }} - digest-cu130: ${{ steps.build-cu130.outputs.digest }} - strategy: - matrix: - variant: - - cuda_version: "12.9.1" - build_type: "all" - grace_blackwell: 1 - runs-on: arm-docker-build-node - steps: - - name: Delete huge unnecessary tools folder - run: rm -rf /opt/hostedtoolcache - - - name: Cleanup workspace (remove root-owned files from prior runs) - run: sudo rm -rf "$GITHUB_WORKSPACE"/* || true - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) - VERSION="${GITHUB_REF_NAME#v}" - fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" - exit 1 - fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - - name: Build ARM64 Framework - id: build-cu129 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target framework_final \ - --platform linux/arm64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=${{ matrix.variant.cuda_version }} \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg GRACE_BLACKWELL=${{ matrix.variant.grace_blackwell }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu129-framework.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu129-framework.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - - name: Build and Push ARM64 Framework (CUDA 13) - id: build-cu130 - run: | - version=${{ steps.version.outputs.version }} - - docker buildx build \ - --target framework_final \ - --platform linux/arm64 \ - --output type=image,name=lmsysorg/sglang,push-by-digest=true,name-canonical=true,push=true \ - -f docker/Dockerfile \ - --build-arg CUDA_VERSION=13.0.1 \ - --build-arg BUILD_TYPE=${{ matrix.variant.build_type }} \ - --build-arg INSTALL_FLASHINFER_JIT_CACHE=1 \ - --build-arg GRACE_BLACKWELL=1 \ - --build-arg SGL_VERSION=${version} \ - --metadata-file /tmp/metadata-cu130-framework.json \ - --no-cache \ - . - - DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata-cu130-framework.json'))['containerimage.digest'])") - echo "Pushed digest: ${DIGEST}" - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - - create-manifests: - runs-on: ubuntu-22.04 - needs: [publish-x86, publish-arm64] - if: github.repository == 'sgl-project/sglang' - environment: "prod" - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get version from tag - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - # Extract version from tag (e.g., v0.5.7 -> 0.5.7) - VERSION="${GITHUB_REF_NAME#v}" - fi - - # Validate version format - if [ -z "$VERSION" ]; then - echo "::error::Version is empty" - exit 1 - fi - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "::error::Invalid version format: $VERSION (expected: X.Y.Z)" - exit 1 - fi - - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - - name: Create multi-arch manifests - run: | - version=${{ steps.version.outputs.version }} - - CU129_AMD64_FW=${{ needs.publish-x86.outputs.digest-cu129 }} - CU130_AMD64_FW=${{ needs.publish-x86.outputs.digest-cu130 }} - CU129_ARM64_FW=${{ needs.publish-arm64.outputs.digest-cu129 }} - CU130_ARM64_FW=${{ needs.publish-arm64.outputs.digest-cu130 }} - - # Create versioned framework manifest (default) - docker buildx imagetools create \ - -t lmsysorg/sglang:v${version} \ - lmsysorg/sglang@${CU129_AMD64_FW} \ - lmsysorg/sglang@${CU129_ARM64_FW} - - # Create latest framework manifest (default) - docker buildx imagetools create \ - -t lmsysorg/sglang:latest \ - lmsysorg/sglang@${CU129_AMD64_FW} \ - lmsysorg/sglang@${CU129_ARM64_FW} - - # Create versioned CUDA 13 framework manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:v${version}-cu130 \ - lmsysorg/sglang@${CU130_AMD64_FW} \ - lmsysorg/sglang@${CU130_ARM64_FW} - - # Create latest CUDA 13 framework manifest - docker buildx imagetools create \ - -t lmsysorg/sglang:latest-cu130 \ - lmsysorg/sglang@${CU130_AMD64_FW} \ - lmsysorg/sglang@${CU130_ARM64_FW} + build-and-publish: + needs: resolve-version + uses: ./.github/workflows/_docker-build-and-publish.yml + with: + docker_target: framework_final + sgl_version: ${{ needs.resolve-version.outputs.version }} + use_environment: prod + image_repo: ${{ inputs.image_repo || 'lmsysorg/sglang' }} + tag_config: | + [ + {"cuda": "cu129", "tags": ["v${{ needs.resolve-version.outputs.version }}", "latest"]}, + {"cuda": "cu130", "tags": ["v${{ needs.resolve-version.outputs.version }}-cu130", "latest-cu130"]} + ] + secrets: inherit