# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: Build Frontend Image on: push: branches: - main - 'release/*.*.*' - "pull-request/[0-9]+" # Note: release/* branches always build (change detection skipped) workflow_call: inputs: skip_change_detection: description: 'Skip changed-files detection and always build (used by release pipeline)' required: false type: boolean default: false image_prefix: description: 'Optional prefix for image tags (e.g., release-0.9.0). When set, images are also tagged as {prefix}-frontend.' required: false type: string default: '' secrets: AWS_ACCOUNT_ID: required: true AWS_DEFAULT_REGION: required: true AWS_ACCESS_KEY_ID: required: true AWS_SECRET_ACCESS_KEY: required: true AZURE_ACR_HOSTNAME: required: true AZURE_ACR_USER: required: true AZURE_ACR_PASSWORD: required: true SCCACHE_S3_BUCKET: required: true concurrency: group: ${{ github.workflow }}-${{ github.ref_name == 'main' && github.run_id || github.ref_name }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} env: BUILDER_NAME: b-${{ github.run_id }}-${{ github.run_attempt }} jobs: changed-files: runs-on: ubuntu-slim outputs: frontend: ${{ steps.changes.outputs.frontend }} builder_name: ${{ steps.export-builder-name.outputs.builder_name }} steps: - name: Checkout code uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: fetch-depth: 0 - name: Check for changes id: changes uses: ./.github/actions/changed-files with: gh_token: ${{ github.token }} - name: Export builder name id: export-builder-name run: | echo "builder_name=${{ env.BUILDER_NAME }}" >> $GITHUB_OUTPUT build-frontend-image: name: Build Frontend Image needs: [changed-files] if: needs.changed-files.outputs.frontend == 'true' || inputs.skip_change_detection == true || startsWith(github.ref_name, 'release/') runs-on: prod-builder-v3 outputs: target_tag: ${{ steps.calculate-target-tag.outputs.target_tag }} env: IMAGE_REGISTRY: ai-dynamo IMAGE_REPOSITORY: dynamo ECR_HOSTNAME: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com TARGET: frontend CUDA_VERSION: "12.9" FRAMEWORK: dynamo PLATFORM: "linux/amd64,linux/arm64" steps: - name: Checkout repository uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Initialize Dynamo Builder uses: ./.github/actions/init-dynamo-builder with: builder_name: ${{ needs.changed-files.outputs.builder_name }} flavor: general all_arch: 'true' - name: Docker Login uses: ./.github/actions/docker-login with: aws_default_region: ${{ secrets.AWS_DEFAULT_REGION }} aws_account_id: ${{ secrets.AWS_ACCOUNT_ID }} azure_acr_hostname: ${{ secrets.AZURE_ACR_HOSTNAME }} azure_acr_user: ${{ secrets.AZURE_ACR_USER }} azure_acr_password: ${{ secrets.AZURE_ACR_PASSWORD }} - name: Build EPP Image id: build-epp-image shell: bash timeout-minutes: 20 run: | set -x EPP_IMAGE_REPO="${{ env.ECR_HOSTNAME }}/${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}" EPP_IMAGE_TAG="${{ github.sha }}" EPP_IMAGE_REF="${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}:${EPP_IMAGE_TAG}" echo "epp_image_ref=${EPP_IMAGE_REF}" >> $GITHUB_OUTPUT CACHE_ARGS="" CACHE_ARGS+="--cache-from type=registry,ref=${ECR_HOSTNAME}/${IMAGE_REGISTRY}/${IMAGE_REPOSITORY}:cache " if [[ "$GITHUB_REF_NAME" == "main" ]]; then CACHE_ARGS+="--cache-to type=registry,ref=${ECR_HOSTNAME}/${IMAGE_REGISTRY}/${IMAGE_REPOSITORY}:cache,mode=max " fi make -C deploy/inference-gateway/epp image-multiarch-push \ IMAGE_REPO="${EPP_IMAGE_REPO}" \ GIT_TAG="${EPP_IMAGE_TAG}" \ DOCKER_PROXY="${ECR_HOSTNAME}/dockerhub/" \ EXTRA_BUILD_ARGS="${CACHE_ARGS}" - name: Generate Dockerfile shell: bash run: | echo "Generating Dockerfile for target: ${{ env.TARGET }} and framework: ${{ env.FRAMEWORK }} " python ./container/render.py \ --target=${{ env.TARGET }} \ --framework=${{ env.FRAMEWORK }} \ --platform=${{ env.PLATFORM }} \ --cuda-version=${{ env.CUDA_VERSION }} \ --show-result \ --output-short-filename - name: Calculate target tag id: calculate-target-tag shell: bash run: | ECR_IMAGE_BASE="${{ env.ECR_HOSTNAME }}/${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}" TARGET_TAG="${{ github.sha }}-${{ env.TARGET }}" echo "ecr_image_base=${ECR_IMAGE_BASE}" >> $GITHUB_OUTPUT echo "default_target_image_uri=${ECR_IMAGE_BASE}:${TARGET_TAG}" >> $GITHUB_OUTPUT echo "epp_image_uri=${{ env.ECR_HOSTNAME }}/${{ steps.build-epp-image.outputs.epp_image_ref }}" >> $GITHUB_OUTPUT echo "target_tag=${TARGET_TAG}" >> $GITHUB_OUTPUT echo "azure_target_image_uri=${{ secrets.AZURE_ACR_HOSTNAME }}/${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}:${TARGET_TAG}" >> $GITHUB_OUTPUT - name: Print Build Container parameters run: | echo "=== Build Container Parameters ===" echo "image_tag: ${{ steps.calculate-target-tag.outputs.default_target_image_uri }}" echo "framework: ${{ env.FRAMEWORK }}" echo "target: ${{ env.TARGET }}" echo "platform: ${{ env.PLATFORM }}" echo "cuda_version: ${{ env.CUDA_VERSION }}" echo "extra_tags: ${{ steps.calculate-target-tag.outputs.azure_target_image_uri }}" echo "epp_image_uri: ${{ steps.calculate-target-tag.outputs.epp_image_uri }}" echo "push_image: true" - name: Build Frontend Container id: build-image timeout-minutes: 30 uses: ./.github/actions/docker-remote-build with: image_tag: ${{ steps.calculate-target-tag.outputs.default_target_image_uri }} framework: ${{ env.FRAMEWORK }} target: ${{ env.TARGET }} platform: ${{ env.PLATFORM }} cuda_version: ${{ env.CUDA_VERSION }} aws_default_region: ${{ secrets.AWS_DEFAULT_REGION }} sccache_s3_bucket: ${{ secrets.SCCACHE_S3_BUCKET }} aws_account_id: ${{ secrets.AWS_ACCOUNT_ID }} aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} push_image: true extra_build_args: | EPP_IMAGE=${{ steps.calculate-target-tag.outputs.epp_image_uri }} extra_tags: | ${{ steps.calculate-target-tag.outputs.azure_target_image_uri }} ${{ github.ref_name == 'main' && format('{0}:main-frontend', steps.calculate-target-tag.outputs.ecr_image_base) || '' }} ${{ github.ref_name == 'main' && format('{0}:main-frontend-{1}', steps.calculate-target-tag.outputs.ecr_image_base, github.sha) || '' }} ${{ inputs.image_prefix != '' && format('{0}:{1}-frontend', steps.calculate-target-tag.outputs.ecr_image_base, inputs.image_prefix) || '' }} - name: Show summary shell: bash run: | echo "### Frontend Image (multi-arch)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Image URI |" >> $GITHUB_STEP_SUMMARY echo "|-----|" >> $GITHUB_STEP_SUMMARY echo "| \`${{ steps.calculate-target-tag.outputs.default_target_image_uri }}\` |" >> $GITHUB_STEP_SUMMARY echo "| \`${{ steps.calculate-target-tag.outputs.azure_target_image_uri }}\` |" >> $GITHUB_STEP_SUMMARY # ============================================================================ # COMPLIANCE — Generate attribution CSVs for dpkg and Python packages # ============================================================================ compliance: needs: [build-frontend-image, changed-files] if: needs.build-frontend-image.result == 'success' strategy: fail-fast: false matrix: include: - arch: amd64 runner: prod-builder-amd-v1 - arch: arm64 runner: prod-tester-arm-v1 name: Compliance frontend-${{ matrix.arch }} runs-on: ${{ matrix.runner }} steps: - name: Checkout repository uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Docker Login uses: ./.github/actions/docker-login with: aws_default_region: ${{ secrets.AWS_DEFAULT_REGION }} aws_account_id: ${{ secrets.AWS_ACCOUNT_ID }} azure_acr_hostname: ${{ secrets.AZURE_ACR_HOSTNAME }} azure_acr_user: ${{ secrets.AZURE_ACR_USER }} azure_acr_password: ${{ secrets.AZURE_ACR_PASSWORD }} - name: Compliance scan uses: ./.github/actions/compliance-scan with: image: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/ai-dynamo/dynamo:${{ needs.build-frontend-image.outputs.target_tag }} artifact_name: compliance-frontend-${{ matrix.arch }} framework: dynamo target: frontend frontend-status-check: runs-on: ubuntu-latest needs: [changed-files, build-frontend-image] if: always() steps: - name: "Check all dependent jobs" run: | echo '${{ toJson(needs) }}' | jq -e 'to_entries | map(.value.result) | all(. as $result | ["success", "skipped"] | any($result == .))' clean-k8s-builder: name: Clean K8s builder if exists runs-on: prod-default-small-v2 if: always() needs: [build-frontend-image, changed-files] steps: - name: Checkout repository uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Create K8s builders (skip bootstrap) uses: ./.github/actions/bootstrap-buildkit continue-on-error: true with: builder_name: ${{ needs.changed-files.outputs.builder_name }} buildkit_worker_addresses: '' # k8s builder skip_bootstrap: true - name: Builder Cleanup in case of k8s builder shell: bash run: | docker buildx rm ${{ needs.changed-files.outputs.builder_name }} || true