name: Build and Push Docker Images on Tag on: push: tags: - "*" workflow_dispatch: # Set restrictive default permissions for all jobs. Jobs that need more permissions # should explicitly declare them. permissions: # Required to checkout the repo on private repos (no-op on public) contents: read # Required for OIDC authentication with AWS id-token: write # zizmor: ignore[excessive-permissions] env: EDGE_TAG: ${{ startsWith(github.ref_name, 'nightly-latest') || github.ref_name == 'edge' }} jobs: # Determine which components to build based on the tag determine-builds: # NOTE: Github-hosted runners have about 20s faster queue times and are preferred here. runs-on: ubuntu-slim timeout-minutes: 90 outputs: build-desktop: ${{ steps.check.outputs.build-desktop }} build-web: ${{ steps.check.outputs.build-web }} build-web-cloud: ${{ steps.check.outputs.build-web-cloud }} build-backend: ${{ steps.check.outputs.build-backend }} build-model-server: ${{ steps.check.outputs.build-model-server }} is-cloud-tag: ${{ steps.check.outputs.is-cloud-tag }} is-beta: ${{ steps.check.outputs.is-beta }} is-beta-standalone: ${{ steps.check.outputs.is-beta-standalone }} is-stable: ${{ steps.check.outputs.is-stable }} is-latest: ${{ steps.check.outputs.is-latest }} is-test-run: ${{ steps.check.outputs.is-test-run }} sanitized-tag: ${{ steps.check.outputs.sanitized-tag }} short-sha: ${{ steps.check.outputs.short-sha }} steps: - name: Checkout (for git tags) uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 fetch-tags: true - name: Setup uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0 with: version: "0.11.25" enable-cache: false - name: Check which components to build and version info id: check env: EVENT_NAME: ${{ github.event_name }} run: | set -eo pipefail TAG="${GITHUB_REF_NAME}" # Sanitize tag name by replacing slashes with hyphens (for Docker tag compatibility) SANITIZED_TAG=$(echo "$TAG" | tr '/' '-') SHORT_SHA="${GITHUB_SHA::7}" # Initialize all flags to false IS_CLOUD=false IS_NIGHTLY=false IS_VERSION_TAG=false IS_STABLE=false IS_BETA=false IS_BETA_STANDALONE=false IS_LATEST=false IS_PROD_TAG=false IS_TEST_RUN=false BUILD_DESKTOP=false BUILD_WEB=false BUILD_WEB_CLOUD=false BUILD_BACKEND=true BUILD_MODEL_SERVER=true # Determine tag type based on pattern matching (do regex checks once) if [[ "$TAG" == *cloud* ]]; then IS_CLOUD=true fi if [[ "$TAG" == nightly* ]]; then IS_NIGHTLY=true fi if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then IS_VERSION_TAG=true fi if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then IS_STABLE=true fi if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-beta(\.[0-9]+)?$ ]]; then IS_BETA=true fi # Determine what to build based on tag type if [[ "$IS_CLOUD" == "true" ]]; then BUILD_WEB_CLOUD=true else BUILD_WEB=true # Only build desktop for semver tags (excluding beta) if [[ "$IS_VERSION_TAG" == "true" ]] && [[ "$IS_BETA" != "true" ]]; then BUILD_DESKTOP=true fi fi # Standalone version checks (for backend/model-server - version excluding cloud tags) if [[ "$IS_BETA" == "true" ]] && [[ "$IS_CLOUD" != "true" ]]; then IS_BETA_STANDALONE=true fi # Determine if this tag should get the "latest" Docker tag. # Only the highest semver stable tag (vX.Y.Z exactly) gets "latest". if [[ "$IS_STABLE" == "true" ]]; then HIGHEST_STABLE=$(uv run --no-sync --with onyx-devtools ods latest-stable-tag) || { echo "::error::Failed to determine highest stable tag via 'ods latest-stable-tag'" exit 1 } if [[ "$TAG" == "$HIGHEST_STABLE" ]]; then IS_LATEST=true fi fi # Determine if this is a production tag # Production tags are: version tags (v1.2.3*) or nightly tags if [[ "$IS_VERSION_TAG" == "true" ]] || [[ "$IS_NIGHTLY" == "true" ]]; then IS_PROD_TAG=true fi # Determine if this is a test run (workflow_dispatch on non-production ref) if [[ "$EVENT_NAME" == "workflow_dispatch" ]] && [[ "$IS_PROD_TAG" != "true" ]]; then IS_TEST_RUN=true fi { echo "build-desktop=$BUILD_DESKTOP" echo "build-web=$BUILD_WEB" echo "build-web-cloud=$BUILD_WEB_CLOUD" echo "build-backend=$BUILD_BACKEND" echo "build-model-server=$BUILD_MODEL_SERVER" echo "is-cloud-tag=$IS_CLOUD" echo "is-beta=$IS_BETA" echo "is-beta-standalone=$IS_BETA_STANDALONE" echo "is-stable=$IS_STABLE" echo "is-latest=$IS_LATEST" echo "is-test-run=$IS_TEST_RUN" echo "sanitized-tag=$SANITIZED_TAG" echo "short-sha=$SHORT_SHA" } >> "$GITHUB_OUTPUT" # Block the release when dependencies carry unignored critical vulnerabilities. # Runs the same `ods audit` gate used on PRs and nightly. Always runs (no skip # condition) so the build jobs that depend on it aren't skipped on test runs; # the audit itself degrades gracefully when the S3 allowlist or Dependabot API # is unavailable. audit-gate: needs: [determine-builds] runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read # checkout + read lockfiles id-token: write # OIDC for fetching the S3 allowlist security-events: read # read Dependabot alerts steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Setup uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0 with: version: "0.11.25" enable-cache: false - name: Configure AWS credentials continue-on-error: true # the audit still runs (with no suppressions) if creds are unavailable uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # ratchet:aws-actions/configure-aws-credentials@v6.2.0 with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Run dependency audit env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: uv run --no-sync --with onyx-devtools ods audit --fail-on=critical check-version-tag: runs-on: ubuntu-slim timeout-minutes: 10 if: ${{ !startsWith(github.ref_name, 'nightly-latest') && github.ref_name != 'edge' && github.event_name != 'workflow_dispatch' }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 - name: Setup uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0 with: version: "0.11.25" # NOTE: This isn't caching much and zizmor suggests this could be poisoned, so disable. enable-cache: false - name: Validate tag is versioned correctly run: | uv run --no-sync --with release-tag tag --check notify-slack-on-tag-check-failure: needs: - check-version-tag if: always() && needs.check-version-tag.result == 'failure' && github.event_name != 'workflow_dispatch' runs-on: ubuntu-slim timeout-minutes: 10 environment: release steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Send Slack notification uses: ./.github/actions/slack-notify with: webhook-url: ${{ secrets.MONITOR_DEPLOYMENTS_WEBHOOK }} failed-jobs: "• check-version-tag" title: "🚨 Version Tag Check Failed" ref-name: ${{ github.ref_name }} # Create GitHub release first, before desktop builds start. # This ensures all desktop matrix jobs upload to the same release instead of # racing to create duplicate releases. create-release: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-desktop == 'true' runs-on: ubuntu-slim timeout-minutes: 10 permissions: contents: write outputs: release-id: ${{ steps.create-release.outputs.id }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Determine release tag id: release-tag env: IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} SHORT_SHA: ${{ needs.determine-builds.outputs.short-sha }} run: | if [ "${IS_TEST_RUN}" == "true" ]; then echo "tag=v0.0.0-dev+${SHORT_SHA}" >> "$GITHUB_OUTPUT" else echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" fi - name: Create GitHub Release id: create-release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.release-tag.outputs.tag }} run: | # Look up an existing release (including drafts) by tag name. # # NOTE: a draft release does not create the underlying git tag until # it is published, so `GET /releases/tags/{tag}` (and # `gh release view `) returns 404 for drafts. We must list # releases instead — that endpoint DOES include drafts — and match # on tag_name. get_release_id() { gh api --paginate "repos/${GITHUB_REPOSITORY}/releases" \ --jq ".[] | select(.tag_name == \"${TAG}\") | .id" | head -n1 } release_id=$(get_release_id) if [ -z "$release_id" ]; then release_id=$(gh api --method POST "repos/${GITHUB_REPOSITORY}/releases" \ -f tag_name="$TAG" \ -f name="$TAG" \ -f body="See the assets to download this version and install." \ -F draft=true \ --jq '.id') fi if [ -z "$release_id" ]; then echo "::error::Failed to resolve release id for tag ${TAG}" exit 1 fi echo "id=${release_id}" >> "$GITHUB_OUTPUT" build-desktop: needs: - determine-builds - create-release if: needs.determine-builds.outputs.build-desktop == 'true' permissions: id-token: write contents: write actions: read strategy: fail-fast: false matrix: include: - platform: "macos-latest" # Build a universal image for macOS. args: "--target universal-apple-darwin" rust-targets: "aarch64-apple-darwin,x86_64-apple-darwin" - platform: "ubuntu-24.04" args: "--bundles deb,rpm" - platform: "ubuntu-24.04-arm" # Only available in public repos. args: "--bundles deb,rpm" - platform: "windows-latest" args: "" # Cross-compiled Windows ARM64 build. Only the NSIS bundle supports # ARM64 (WiX/MSI does not), hence --bundles nsis. - platform: "windows-latest" args: "--target aarch64-pc-windows-msvc --bundles nsis" rust-targets: "aarch64-pc-windows-msvc" runs-on: ${{ matrix.platform }} timeout-minutes: 90 environment: release steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: # NOTE: persist-credentials is needed for tauri-action to upload assets to GitHub releases. persist-credentials: true # zizmor: ignore[artipacked] - name: Configure AWS credentials if: startsWith(matrix.platform, 'macos-') uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets if: startsWith(matrix.platform, 'macos-') uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | APPLE_ID, deploy/apple-id APPLE_PASSWORD, deploy/apple-password APPLE_CERTIFICATE, deploy/apple-certificate APPLE_CERTIFICATE_PASSWORD, deploy/apple-certificate-password KEYCHAIN_PASSWORD, deploy/keychain-password APPLE_TEAM_ID, deploy/apple-team-id parse-json-secrets: true - name: install dependencies (ubuntu only) if: startsWith(matrix.platform, 'ubuntu-') run: | sudo apt-get update sudo apt-get install -y \ build-essential \ libglib2.0-dev \ libgirepository1.0-dev \ libgtk-3-dev \ libjavascriptcoregtk-4.1-dev \ libwebkit2gtk-4.1-dev \ libayatana-appindicator3-dev \ gobject-introspection \ pkg-config \ curl \ xdg-utils - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # ratchet:oven-sh/setup-bun@v2 # zizmor: ignore[cache-poisoning] with: bun-version: "1.3.13" - name: install Rust stable uses: dtolnay/rust-toolchain@6d9817901c499d6b02debbb57edb38d33daa680b # zizmor: ignore[impostor-commit] with: # Extra targets for jobs that cross-compile (macOS universal, Windows ARM64). # Jobs building only for the host architecture leave this empty. targets: ${{ matrix.rust-targets || '' }} - name: install frontend dependencies run: bun install --frozen-lockfile - name: Inject version (Unix) if: runner.os != 'Windows' working-directory: ./desktop env: SHORT_SHA: ${{ needs.determine-builds.outputs.short-sha }} IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} run: | if [ "${IS_TEST_RUN}" == "true" ]; then VERSION="0.0.0-dev+${SHORT_SHA}" else VERSION="${GITHUB_REF_NAME#v}" fi echo "Injecting version: $VERSION" # Update Cargo.toml sed "s/^version = .*/version = \"$VERSION\"/" src-tauri/Cargo.toml > src-tauri/Cargo.toml.tmp mv src-tauri/Cargo.toml.tmp src-tauri/Cargo.toml # Update tauri.conf.json jq --arg v "$VERSION" '.version = $v' src-tauri/tauri.conf.json > src-tauri/tauri.conf.json.tmp mv src-tauri/tauri.conf.json.tmp src-tauri/tauri.conf.json # Update package.json jq --arg v "$VERSION" '.version = $v' package.json > package.json.tmp mv package.json.tmp package.json echo "Versions set to: $VERSION" - name: Inject version (Windows) if: runner.os == 'Windows' working-directory: ./desktop shell: pwsh env: IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} run: | # Windows MSI requires numeric-only build metadata, so we skip the SHA suffix if ($env:IS_TEST_RUN -eq "true") { $VERSION = "0.0.0" } else { # Strip 'v' prefix and any pre-release suffix (e.g., -beta.13) for MSI compatibility $VERSION = "$env:GITHUB_REF_NAME" -replace '^v', '' -replace '-.*$', '' } Write-Host "Injecting version: $VERSION" # Update Cargo.toml $cargo = Get-Content src-tauri/Cargo.toml -Raw $cargo = $cargo -replace '(?m)^version = .*', "version = `"$VERSION`"" Set-Content src-tauri/Cargo.toml $cargo -NoNewline # Update tauri.conf.json $json = Get-Content src-tauri/tauri.conf.json | ConvertFrom-Json $json.version = $VERSION $json | ConvertTo-Json -Depth 100 | Set-Content src-tauri/tauri.conf.json # Update package.json $pkg = Get-Content package.json | ConvertFrom-Json $pkg.version = $VERSION $pkg | ConvertTo-Json -Depth 100 | Set-Content package.json Write-Host "Versions set to: $VERSION" - name: Import Apple Developer Certificate if: startsWith(matrix.platform, 'macos-') run: | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u build.keychain security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain security find-identity -v -p codesigning build.keychain - name: Verify Certificate if: startsWith(matrix.platform, 'macos-') run: | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep -E "(Developer ID Application|Apple Distribution|Apple Development)" | head -n 1) CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported." - uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # ratchet:tauri-apps/tauri-action@action-v0.6.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_ID: ${{ env.APPLE_ID }} APPLE_PASSWORD: ${{ env.APPLE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} with: projectPath: desktop # Use the release created by the create-release job to avoid race conditions # when multiple matrix jobs try to create/update the same release simultaneously releaseId: ${{ needs.create-release.outputs.release-id }} assetNamePattern: "[name]_[arch][ext]" args: ${{ matrix.args }} build-web-amd64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-web == 'true' runs-on: - runs-on - runner=4cpu-linux-x64 - run-id=${{ github.run_id }}-web-amd64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same # Docker account credentials (the account must have access to the DHI catalog). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./web file: ./web/Dockerfile platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} NODE_OPTIONS=--max-old-space-size=8192 cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} build-web-arm64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-web == 'true' runs-on: - runs-on - runner=4cpu-linux-arm64 - run-id=${{ github.run_id }}-web-arm64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same # Docker account credentials (the account must have access to the DHI catalog). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./web file: ./web/Dockerfile platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} NODE_OPTIONS=--max-old-space-size=8192 cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} merge-web: needs: - determine-builds - build-web-amd64 - build-web-arm64 - image-audit if: always() && needs.build-web-amd64.result == 'success' && needs.build-web-arm64.result == 'success' && needs.image-audit.result == 'success' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-merge-web - extras=ecr-cache timeout-minutes: 90 environment: release env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false tags: | type=raw,value=${{ needs.determine-builds.outputs.is-test-run == 'true' && format('web-{0}', needs.determine-builds.outputs.sanitized-tag) || github.ref_name }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-latest == 'true' && 'latest' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && env.EDGE_TAG == 'true' && 'edge' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-beta == 'true' && 'beta' || '' }} type=semver,pattern={{version}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}}.{{minor}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} - name: Create and push manifest env: IMAGE_REPO: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} AMD64_DIGEST: ${{ needs.build-web-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.build-web-arm64.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} run: | IMAGES="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ $(printf '%s\n' "${META_TAGS}" | xargs -I {} echo -t {}) \ $IMAGES build-web-cloud-amd64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-web-cloud == 'true' runs-on: - runs-on - runner=4cpu-linux-x64 - run-id=${{ github.run_id }}-web-cloud-amd64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same # Docker account credentials (the account must have access to the DHI catalog). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./web file: ./web/Dockerfile platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} NEXT_PUBLIC_CLOUD_ENABLED=true WEB_FRAME_PROTECTION_ENABLED=false NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.POSTHOG_KEY }} NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.POSTHOG_HOST }} NEXT_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }} NEXT_PUBLIC_RECAPTCHA_SITE_KEY=${{ vars.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }} NEXT_PUBLIC_GTM_ENABLED=true NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED=true NEXT_PUBLIC_INCLUDE_ERROR_POPUP_SUPPORT_LINK=true NODE_OPTIONS=--max-old-space-size=8192 SENTRY_RELEASE=${{ github.sha }} secrets: | sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }} cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} build-web-cloud-arm64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-web-cloud == 'true' runs-on: - runs-on - runner=4cpu-linux-arm64 - run-id=${{ github.run_id }}-web-cloud-arm64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same # Docker account credentials (the account must have access to the DHI catalog). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./web file: ./web/Dockerfile platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} NEXT_PUBLIC_CLOUD_ENABLED=true WEB_FRAME_PROTECTION_ENABLED=false NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.POSTHOG_KEY }} NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.POSTHOG_HOST }} NEXT_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }} NEXT_PUBLIC_RECAPTCHA_SITE_KEY=${{ vars.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }} NEXT_PUBLIC_GTM_ENABLED=true NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED=true NEXT_PUBLIC_INCLUDE_ERROR_POPUP_SUPPORT_LINK=true NODE_OPTIONS=--max-old-space-size=8192 SENTRY_RELEASE=${{ github.sha }} secrets: | sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }} cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} merge-web-cloud: needs: - determine-builds - build-web-cloud-amd64 - build-web-cloud-arm64 - image-audit if: always() && needs.build-web-cloud-amd64.result == 'success' && needs.build-web-cloud-arm64.result == 'success' && needs.image-audit.result == 'success' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-merge-web-cloud - extras=ecr-cache timeout-minutes: 90 environment: release env: REGISTRY_IMAGE: onyxdotapp/onyx-web-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false tags: | type=raw,value=${{ needs.determine-builds.outputs.is-test-run == 'true' && format('web-cloud-{0}', needs.determine-builds.outputs.sanitized-tag) || github.ref_name }} - name: Create and push manifest env: IMAGE_REPO: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} AMD64_DIGEST: ${{ needs.build-web-cloud-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.build-web-cloud-arm64.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} run: | IMAGES="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ $(printf '%s\n' "${META_TAGS}" | xargs -I {} echo -t {}) \ $IMAGES build-backend-amd64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-backend == 'true' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-backend-amd64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-backend steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./backend file: ./backend/Dockerfile platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} build-backend-arm64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-backend == 'true' runs-on: - runs-on - runner=2cpu-linux-arm64 - run-id=${{ github.run_id }}-backend-arm64 - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-backend steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./backend file: ./backend/Dockerfile platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} merge-backend: needs: - determine-builds - build-backend-amd64 - build-backend-arm64 - image-audit if: always() && needs.build-backend-amd64.result == 'success' && needs.build-backend-arm64.result == 'success' && needs.image-audit.result == 'success' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-merge-backend - extras=ecr-cache timeout-minutes: 90 environment: release env: REGISTRY_IMAGE: onyxdotapp/onyx-backend steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false tags: | type=raw,value=${{ needs.determine-builds.outputs.is-test-run == 'true' && format('backend-{0}', needs.determine-builds.outputs.sanitized-tag) || github.ref_name }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-latest == 'true' && 'latest' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && env.EDGE_TAG == 'true' && 'edge' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-beta-standalone == 'true' && 'beta' || '' }} type=semver,pattern={{version}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}}.{{minor}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} - name: Create and push manifest env: IMAGE_REPO: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} AMD64_DIGEST: ${{ needs.build-backend-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.build-backend-arm64.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} run: | IMAGES="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ $(printf '%s\n' "${META_TAGS}" | xargs -I {} echo -t {}) \ $IMAGES build-model-server-amd64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-model-server == 'true' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-model-server-amd64 - volume=40gb - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-model-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 with: buildkitd-flags: ${{ vars.DOCKER_DEBUG == 'true' && '--debug' || '' }} - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # Dockerfile.model_server pulls its hardened Python base from dhi.io, authenticated # with the same Docker account credentials (the account must have DHI catalog access). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a env: DEBUG: ${{ vars.DOCKER_DEBUG == 'true' && 1 || 0 }} with: context: ./backend file: ./backend/Dockerfile.model_server platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ env.EDGE_TAG != 'true' && vars.MODEL_SERVER_NO_CACHE == 'true' }} build-model-server-arm64: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.build-model-server == 'true' runs-on: - runs-on - runner=2cpu-linux-arm64 - run-id=${{ github.run_id }}-model-server-arm64 - volume=40gb - extras=ecr-cache timeout-minutes: 90 environment: release outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/onyx-model-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 with: buildkitd-flags: ${{ vars.DOCKER_DEBUG == 'true' && '--debug' || '' }} - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} # Dockerfile.model_server pulls its hardened Python base from dhi.io, authenticated # with the same Docker account credentials (the account must have DHI catalog access). - name: Login to Docker Hardened Images (dhi.io) uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: registry: dhi.io username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a env: DEBUG: ${{ vars.DOCKER_DEBUG == 'true' && 1 || 0 }} with: context: ./backend file: ./backend/Dockerfile.model_server platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} build-args: | ONYX_VERSION=${{ github.ref_name }} cache-from: | type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ env.EDGE_TAG != 'true' && vars.MODEL_SERVER_NO_CACHE == 'true' }} merge-model-server: needs: - determine-builds - build-model-server-amd64 - build-model-server-arm64 - image-audit if: always() && needs.build-model-server-amd64.result == 'success' && needs.build-model-server-arm64.result == 'success' && needs.image-audit.result == 'success' runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-merge-model-server - extras=ecr-cache timeout-minutes: 90 environment: release env: REGISTRY_IMAGE: onyxdotapp/onyx-model-server steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false tags: | type=raw,value=${{ needs.determine-builds.outputs.is-test-run == 'true' && format('model-server-{0}', needs.determine-builds.outputs.sanitized-tag) || github.ref_name }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-latest == 'true' && 'latest' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && env.EDGE_TAG == 'true' && 'edge' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-beta-standalone == 'true' && 'beta' || '' }} type=semver,pattern={{version}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}}.{{minor}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} - name: Create and push manifest env: IMAGE_REPO: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} AMD64_DIGEST: ${{ needs.build-model-server-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.build-model-server-arm64.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} run: | IMAGES="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ $(printf '%s\n' "${META_TAGS}" | xargs -I {} echo -t {}) \ $IMAGES prepare-sandbox: needs: - determine-builds - audit-gate if: needs.determine-builds.outputs.is-test-run == 'true' || (needs.determine-builds.outputs.is-stable == 'true' || needs.determine-builds.outputs.is-beta-standalone == 'true' || needs.determine-builds.outputs.is-cloud-tag == 'true' || startsWith(github.ref_name, 'nightly-latest') || github.ref_name == 'edge') runs-on: ubuntu-slim timeout-minutes: 10 environment: release permissions: contents: read id-token: write outputs: context-tag: ${{ steps.context.outputs.context-tag }} context-exists: ${{ steps.exists.outputs.context-exists }} env: REGISTRY_IMAGE: onyxdotapp/sandbox SANDBOX_CONTEXT: backend/onyx/server/features/build/sandbox/image steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Compute sandbox context tag id: context run: | set -euo pipefail digest="$(git ls-files -s -z "$SANDBOX_CONTEXT" | sort -z | sha256sum | cut -d' ' -f1)" echo "context-tag=ctx-${digest:0:20}" >> "$GITHUB_OUTPUT" - name: Configure AWS credentials if: needs.determine-builds.outputs.is-test-run != 'true' && vars.DOCKER_NO_CACHE != 'true' uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets if: needs.determine-builds.outputs.is-test-run != 'true' && vars.DOCKER_NO_CACHE != 'true' uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Login to Docker Hub if: needs.determine-builds.outputs.is-test-run != 'true' && vars.DOCKER_NO_CACHE != 'true' uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Check for existing context image id: exists env: CONTEXT_TAG: ${{ steps.context.outputs.context-tag }} DOCKER_NO_CACHE: ${{ vars.DOCKER_NO_CACHE == 'true' && 'true' || 'false' }} IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} run: | if [ "${IS_TEST_RUN}" = "true" ] || [ "${DOCKER_NO_CACHE}" = "true" ]; then echo "context-exists=false" >> "$GITHUB_OUTPUT" exit 0 fi if docker buildx imagetools inspect "${REGISTRY_IMAGE}:${CONTEXT_TAG}" >/dev/null 2>&1; then echo "context-exists=true" >> "$GITHUB_OUTPUT" else echo "context-exists=false" >> "$GITHUB_OUTPUT" fi build-sandbox-amd64: needs: - determine-builds - prepare-sandbox if: needs.prepare-sandbox.result == 'success' && needs.prepare-sandbox.outputs.context-exists != 'true' runs-on: - runs-on - runner=4cpu-linux-x64 - run-id=${{ github.run_id }}-sandbox-amd64 - extras=ecr-cache timeout-minutes: 90 environment: release permissions: contents: read id-token: write outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/sandbox steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push AMD64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./backend/onyx/server/features/build/sandbox/image file: ./backend/onyx/server/features/build/sandbox/image/Dockerfile platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} cache-from: | type=registry,ref=${{ env.BASE_IMAGE_REGISTRY }}/onyxdotapp/sandbox:edge type=registry,ref=${{ env.BASE_IMAGE_REGISTRY }}/onyxdotapp/sandbox:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} build-sandbox-arm64: needs: - determine-builds - prepare-sandbox if: needs.prepare-sandbox.result == 'success' && needs.prepare-sandbox.outputs.context-exists != 'true' runs-on: - runs-on - runner=4cpu-linux-arm64 - run-id=${{ github.run_id }}-sandbox-arm64 - extras=ecr-cache timeout-minutes: 90 environment: release permissions: contents: read id-token: write outputs: digest: ${{ steps.build.outputs.digest }} env: REGISTRY_IMAGE: onyxdotapp/sandbox steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Build and push ARM64 id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: ./backend/onyx/server/features/build/sandbox/image file: ./backend/onyx/server/features/build/sandbox/image/Dockerfile platforms: linux/arm64 labels: ${{ steps.meta.outputs.labels }} cache-from: | type=registry,ref=${{ env.BASE_IMAGE_REGISTRY }}/onyxdotapp/sandbox:edge type=registry,ref=${{ env.BASE_IMAGE_REGISTRY }}/onyxdotapp/sandbox:latest cache-to: type=inline outputs: type=image,name=${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true no-cache: ${{ vars.DOCKER_NO_CACHE == 'true' }} merge-sandbox: needs: - determine-builds - prepare-sandbox - build-sandbox-amd64 - build-sandbox-arm64 - image-audit if: always() && needs.prepare-sandbox.result == 'success' && needs.image-audit.result == 'success' && (needs.prepare-sandbox.outputs.context-exists == 'true' || (needs.build-sandbox-amd64.result == 'success' && needs.build-sandbox-arm64.result == 'success')) runs-on: - runs-on - runner=2cpu-linux-x64 - run-id=${{ github.run_id }}-merge-sandbox - extras=ecr-cache timeout-minutes: 90 environment: release permissions: id-token: write env: REGISTRY_IMAGE: onyxdotapp/sandbox steps: - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Get AWS Secrets uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 with: secret-ids: | DOCKER_USERNAME, deploy/docker-username DOCKER_TOKEN, deploy/docker-token parse-json-secrets: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # ratchet:docker/setup-buildx-action@v4 - name: Login to Docker Hub uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} flavor: | latest=false tags: | type=raw,value=${{ needs.determine-builds.outputs.is-test-run == 'true' && format('sandbox-{0}', needs.determine-builds.outputs.sanitized-tag) || github.ref_name }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-latest == 'true' && 'latest' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && env.EDGE_TAG == 'true' && 'edge' || '' }} type=raw,value=${{ needs.determine-builds.outputs.is-test-run != 'true' && needs.determine-builds.outputs.is-beta-standalone == 'true' && 'beta' || '' }} type=semver,pattern={{version}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}}.{{minor}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} type=semver,pattern={{major}},enable=${{ needs.determine-builds.outputs.is-stable == 'true' }} - name: Create and push manifest env: IMAGE_REPO: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }} CONTEXT_TAG: ${{ needs.prepare-sandbox.outputs.context-tag }} AMD64_DIGEST: ${{ needs.build-sandbox-amd64.outputs.digest }} ARM64_DIGEST: ${{ needs.build-sandbox-arm64.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} DOCKER_NO_CACHE: ${{ vars.DOCKER_NO_CACHE == 'true' && 'true' || 'false' }} IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} run: | mapfile -t tags < <(printf '%s\n' "${META_TAGS}" | sed '/^$/d') tag_args=() for tag in "${tags[@]}"; do tag_args+=("-t" "$tag") done if [[ "$IS_TEST_RUN" == "true" ]]; then if [[ -z "$AMD64_DIGEST" || -z "$ARM64_DIGEST" ]]; then echo "::error::Missing sandbox image digest(s); cannot create test manifest" exit 1 fi images="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ "${tag_args[@]}" \ $images elif [[ "$DOCKER_NO_CACHE" != "true" ]] && docker buildx imagetools inspect "${REGISTRY_IMAGE}:${CONTEXT_TAG}" >/dev/null 2>&1; then docker buildx imagetools create \ "${tag_args[@]}" \ "${REGISTRY_IMAGE}:${CONTEXT_TAG}" else if [[ -z "$AMD64_DIGEST" || -z "$ARM64_DIGEST" ]]; then echo "::error::Missing sandbox image digest(s); cannot create ${CONTEXT_TAG}" exit 1 fi tag_args=("-t" "${REGISTRY_IMAGE}:${CONTEXT_TAG}" "${tag_args[@]}") images="${IMAGE_REPO}@${AMD64_DIGEST} ${IMAGE_REPO}@${ARM64_DIGEST}" docker buildx imagetools create \ "${tag_args[@]}" \ $images fi dispatch-cloud-deployment: needs: - determine-builds - merge-web-cloud - merge-backend - merge-model-server - merge-sandbox if: >- always() && needs.merge-web-cloud.result == 'success' && needs.merge-backend.result == 'success' && needs.merge-model-server.result == 'success' && needs.merge-sandbox.result == 'success' && needs.determine-builds.outputs.is-cloud-tag == 'true' && needs.determine-builds.outputs.is-test-run != 'true' # NOTE: Github-hosted runners have about 20s faster queue times and are preferred here. runs-on: ubuntu-slim timeout-minutes: 10 permissions: {} steps: - name: Resolve dispatch target id: target env: CLOUD_DEPLOYMENT_REPO: ${{ vars.CLOUD_DEPLOYMENT_REPO }} run: | if [ -z "$CLOUD_DEPLOYMENT_REPO" ]; then echo "::error::CLOUD_DEPLOYMENT_REPO is not set" exit 1 fi echo "owner=${CLOUD_DEPLOYMENT_REPO%%/*}" >> "$GITHUB_OUTPUT" echo "repo=${CLOUD_DEPLOYMENT_REPO#*/}" >> "$GITHUB_OUTPUT" - name: Mint GitHub App installation token id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: client-id: ${{ vars.CLOUD_DEPLOYMENT_APP_ID }} private-key: ${{ secrets.CLOUD_DEPLOYMENT_APP_PRIVATE_KEY }} # Scope the token to the same repo the dispatch targets, derived from CLOUD_DEPLOYMENT_REPO. owner: ${{ steps.target.outputs.owner }} repositories: ${{ steps.target.outputs.repo }} permission-contents: write - name: Trigger new-cloud-image dispatch env: GH_TOKEN: ${{ steps.app-token.outputs.token }} CLOUD_DEPLOYMENT_REPO: ${{ vars.CLOUD_DEPLOYMENT_REPO }} VERSION: ${{ github.ref_name }} run: | gh api "repos/${CLOUD_DEPLOYMENT_REPO}/dispatches" \ -f event_type=new-cloud-image \ -F "client_payload[version]=${VERSION}" # Scan each freshly built image (amd64 digest) via `ods audit image`, reusing # osv-scanner and the shared S3 allowlist from the dependency `audit-gate`. # Runs before merge-* and every merge-* depends on it, so an unsuppressed # CRITICAL blocks the public tags. SARIF goes to the Security tab regardless. image-audit: needs: - determine-builds - build-web-amd64 - build-web-cloud-amd64 - build-backend-amd64 - build-model-server-amd64 - build-sandbox-amd64 if: >- always() && !cancelled() && (needs.build-web-amd64.result == 'success' || needs.build-web-cloud-amd64.result == 'success' || needs.build-backend-amd64.result == 'success' || needs.build-model-server-amd64.result == 'success' || needs.build-sandbox-amd64.result == 'success') runs-on: - runs-on # amd64 to match the images scanned: `ods audit image` uses the docker CLI, # which won't pull an amd64 image onto an arm64 host. - runner=2cpu-linux-x64 # osv-scanner pulls the image and writes a full `docker save` tar (~2x its # size); size the root volume up so large images don't fill the disk. - volume=60gb - run-id=${{ github.run_id }}-image-audit-${{ matrix.component }} - extras=ecr-cache permissions: contents: read # checkout + uv project context id-token: write # OIDC for fetching the S3 allowlist security-events: write # SARIF uploads timeout-minutes: 15 strategy: fail-fast: false matrix: include: - component: web registry-image: onyxdotapp/onyx-web-server - component: web-cloud registry-image: onyxdotapp/onyx-web-server - component: backend registry-image: onyxdotapp/onyx-backend - component: model-server registry-image: onyxdotapp/onyx-model-server - component: sandbox registry-image: onyxdotapp/sandbox steps: - name: Check if this scan should run id: should-run run: | case "$COMPONENT" in web) RESULT="$BUILD_WEB" ;; web-cloud) RESULT="$BUILD_WEB_CLOUD" ;; backend) RESULT="$BUILD_BACKEND" ;; model-server) RESULT="$BUILD_MODEL_SERVER" ;; sandbox) RESULT="$BUILD_SANDBOX" ;; esac if [ "$RESULT" == "success" ]; then echo "run=true" >> "$GITHUB_OUTPUT" else echo "run=false" >> "$GITHUB_OUTPUT" fi env: COMPONENT: ${{ matrix.component }} BUILD_WEB: ${{ needs.build-web-amd64.result }} BUILD_WEB_CLOUD: ${{ needs.build-web-cloud-amd64.result }} BUILD_BACKEND: ${{ needs.build-backend-amd64.result }} BUILD_MODEL_SERVER: ${{ needs.build-model-server-amd64.result }} BUILD_SANDBOX: ${{ needs.build-sandbox-amd64.result }} - uses: runs-on/action@4e5f72399b6b17f2e79c511c1b38a315a64d22dc if: steps.should-run.outputs.run == 'true' - name: Checkout if: steps.should-run.outputs.run == 'true' uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Setup uv if: steps.should-run.outputs.run == 'true' uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0 with: version: "0.11.25" enable-cache: false - name: Configure AWS credentials if: steps.should-run.outputs.run == 'true' continue-on-error: true # the audit still runs (with no suppressions) if creds are unavailable uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # ratchet:aws-actions/configure-aws-credentials@v6.2.0 with: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} aws-region: us-east-2 - name: Login to Docker Hub if: steps.should-run.outputs.run == 'true' uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} # Scan the by-digest image the build pushed, before merge-* tags it (same # content in the ECR cache for test runs). - name: Determine scan image if: steps.should-run.outputs.run == 'true' id: scan-image run: | case "$COMPONENT" in web) DIGEST="$DIGEST_WEB" ;; web-cloud) DIGEST="$DIGEST_WEB_CLOUD" ;; backend) DIGEST="$DIGEST_BACKEND" ;; model-server) DIGEST="$DIGEST_MODEL_SERVER" ;; sandbox) DIGEST="$DIGEST_SANDBOX" ;; esac if [ "$IS_TEST_RUN" == "true" ]; then echo "image=${RUNS_ON_ECR_CACHE}@${DIGEST}" >> "$GITHUB_OUTPUT" else echo "image=docker.io/${REGISTRY_IMAGE}@${DIGEST}" >> "$GITHUB_OUTPUT" fi env: COMPONENT: ${{ matrix.component }} IS_TEST_RUN: ${{ needs.determine-builds.outputs.is-test-run }} REGISTRY_IMAGE: ${{ matrix.registry-image }} DIGEST_WEB: ${{ needs.build-web-amd64.outputs.digest }} DIGEST_WEB_CLOUD: ${{ needs.build-web-cloud-amd64.outputs.digest }} DIGEST_BACKEND: ${{ needs.build-backend-amd64.outputs.digest }} DIGEST_MODEL_SERVER: ${{ needs.build-model-server-amd64.outputs.digest }} DIGEST_SANDBOX: ${{ needs.build-sandbox-amd64.outputs.digest }} - name: Run image vulnerability audit if: steps.should-run.outputs.run == 'true' # Exits non-zero on unsuppressed CRITICAL (gates the release). --format # writes SARIF to stdout (redirected to the file for the Security tab # upload) and the human-readable report + runbook to stderr, so the CI log # shows which advisories blocked and how to resolve/suppress them. On a # hard scan failure nothing is written, so seed a valid empty SARIF to keep # the always() upload below from choking. run: | uv run --no-sync --with onyx-devtools ods audit image "$IMAGE" \ --fail-on=critical --format=sarif,text > image-audit.sarif || status=$? if [ ! -s image-audit.sarif ]; then # $schema is a literal SARIF key, not a shell variable. # shellcheck disable=SC2016 echo '{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[]}' > image-audit.sarif fi exit "${status:-0}" env: IMAGE: ${{ steps.scan-image.outputs.image }} # Rolling main-HEAD builds (nightly-latest / edge) map to refs/heads/main so # the Security tab stays authoritative for main and alerts auto-resolve on # later main builds. Test runs (manual workflow_dispatch) upload against their # own ref/sha instead, so the scan + upload can be verified without polluting # main's alerts. Release/beta tags sit on older commits — they still gate, they # just don't upload. - name: Upload audit results to GitHub Security tab if: >- always() && steps.should-run.outputs.run == 'true' && ((env.EDGE_TAG == 'true' && needs.determine-builds.outputs.is-test-run != 'true') || needs.determine-builds.outputs.is-test-run == 'true') uses: github/codeql-action/upload-sarif@ba454b8ab46733eb6145342877cd148270bb77ab with: sarif_file: image-audit.sarif category: image-audit-${{ matrix.component }} ref: ${{ needs.determine-builds.outputs.is-test-run == 'true' && github.ref || 'refs/heads/main' }} sha: ${{ github.sha }} notify-slack-on-failure: needs: - determine-builds - audit-gate - create-release - build-desktop - build-web-amd64 - build-web-arm64 - merge-web - build-web-cloud-amd64 - build-web-cloud-arm64 - merge-web-cloud - build-backend-amd64 - build-backend-arm64 - merge-backend - build-model-server-amd64 - build-model-server-arm64 - merge-model-server - prepare-sandbox - build-sandbox-amd64 - build-sandbox-arm64 - merge-sandbox - image-audit if: always() && (needs.audit-gate.result == 'failure' || needs.create-release.result == 'failure' || needs.build-desktop.result == 'failure' || needs.build-web-amd64.result == 'failure' || needs.build-web-arm64.result == 'failure' || needs.merge-web.result == 'failure' || needs.build-web-cloud-amd64.result == 'failure' || needs.build-web-cloud-arm64.result == 'failure' || needs.merge-web-cloud.result == 'failure' || needs.build-backend-amd64.result == 'failure' || needs.build-backend-arm64.result == 'failure' || needs.merge-backend.result == 'failure' || needs.build-model-server-amd64.result == 'failure' || needs.build-model-server-arm64.result == 'failure' || needs.merge-model-server.result == 'failure' || needs.prepare-sandbox.result == 'failure' || needs.build-sandbox-amd64.result == 'failure' || needs.build-sandbox-arm64.result == 'failure' || needs.merge-sandbox.result == 'failure' || needs.image-audit.result == 'failure') && needs.determine-builds.outputs.is-test-run != 'true' # NOTE: Github-hosted runners have about 20s faster queue times and are preferred here. runs-on: ubuntu-slim timeout-minutes: 90 environment: release steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v6 with: persist-credentials: false - name: Determine failed jobs id: failed-jobs shell: bash run: | FAILED_JOBS="" if [ "${NEEDS_AUDIT_GATE_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• audit-gate\\n" fi if [ "${NEEDS_CREATE_RELEASE_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• create-release\\n" fi if [ "${NEEDS_BUILD_DESKTOP_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-desktop\\n" fi if [ "${NEEDS_BUILD_WEB_AMD64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-web-amd64\\n" fi if [ "${NEEDS_BUILD_WEB_ARM64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-web-arm64\\n" fi if [ "${NEEDS_MERGE_WEB_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• merge-web\\n" fi if [ "${NEEDS_BUILD_WEB_CLOUD_AMD64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-web-cloud-amd64\\n" fi if [ "${NEEDS_BUILD_WEB_CLOUD_ARM64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-web-cloud-arm64\\n" fi if [ "${NEEDS_MERGE_WEB_CLOUD_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• merge-web-cloud\\n" fi if [ "${NEEDS_BUILD_BACKEND_AMD64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-backend-amd64\\n" fi if [ "${NEEDS_BUILD_BACKEND_ARM64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-backend-arm64\\n" fi if [ "${NEEDS_MERGE_BACKEND_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• merge-backend\\n" fi if [ "${NEEDS_BUILD_MODEL_SERVER_AMD64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-model-server-amd64\\n" fi if [ "${NEEDS_BUILD_MODEL_SERVER_ARM64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-model-server-arm64\\n" fi if [ "${NEEDS_MERGE_MODEL_SERVER_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• merge-model-server\\n" fi if [ "${NEEDS_PREPARE_SANDBOX_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• prepare-sandbox\\n" fi if [ "${NEEDS_BUILD_SANDBOX_AMD64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-sandbox-amd64\\n" fi if [ "${NEEDS_BUILD_SANDBOX_ARM64_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• build-sandbox-arm64\\n" fi if [ "${NEEDS_MERGE_SANDBOX_RESULT}" == "failure" ]; then FAILED_JOBS="${FAILED_JOBS}• merge-sandbox\\n" fi # Remove trailing \n and set output FAILED_JOBS=$(printf '%s' "$FAILED_JOBS" | sed 's/\\n$//') echo "jobs=$FAILED_JOBS" >> "$GITHUB_OUTPUT" env: NEEDS_AUDIT_GATE_RESULT: ${{ needs.audit-gate.result }} NEEDS_CREATE_RELEASE_RESULT: ${{ needs.create-release.result }} NEEDS_BUILD_DESKTOP_RESULT: ${{ needs.build-desktop.result }} NEEDS_BUILD_WEB_AMD64_RESULT: ${{ needs.build-web-amd64.result }} NEEDS_BUILD_WEB_ARM64_RESULT: ${{ needs.build-web-arm64.result }} NEEDS_MERGE_WEB_RESULT: ${{ needs.merge-web.result }} NEEDS_BUILD_WEB_CLOUD_AMD64_RESULT: ${{ needs.build-web-cloud-amd64.result }} NEEDS_BUILD_WEB_CLOUD_ARM64_RESULT: ${{ needs.build-web-cloud-arm64.result }} NEEDS_MERGE_WEB_CLOUD_RESULT: ${{ needs.merge-web-cloud.result }} NEEDS_BUILD_BACKEND_AMD64_RESULT: ${{ needs.build-backend-amd64.result }} NEEDS_BUILD_BACKEND_ARM64_RESULT: ${{ needs.build-backend-arm64.result }} NEEDS_MERGE_BACKEND_RESULT: ${{ needs.merge-backend.result }} NEEDS_BUILD_MODEL_SERVER_AMD64_RESULT: ${{ needs.build-model-server-amd64.result }} NEEDS_BUILD_MODEL_SERVER_ARM64_RESULT: ${{ needs.build-model-server-arm64.result }} NEEDS_MERGE_MODEL_SERVER_RESULT: ${{ needs.merge-model-server.result }} NEEDS_PREPARE_SANDBOX_RESULT: ${{ needs.prepare-sandbox.result }} NEEDS_BUILD_SANDBOX_AMD64_RESULT: ${{ needs.build-sandbox-amd64.result }} NEEDS_BUILD_SANDBOX_ARM64_RESULT: ${{ needs.build-sandbox-arm64.result }} NEEDS_MERGE_SANDBOX_RESULT: ${{ needs.merge-sandbox.result }} - name: Send Slack notification uses: ./.github/actions/slack-notify with: webhook-url: ${{ secrets.MONITOR_DEPLOYMENTS_WEBHOOK }} failed-jobs: ${{ steps.failed-jobs.outputs.jobs }} title: "🚨 Deployment Workflow Failed" ref-name: ${{ github.ref_name }}