From 7a7a174366a49f426c2311b8852a8190bc0c39fe Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 20 May 2026 10:40:17 -0400 Subject: [PATCH 1/7] Build images as multi-arch (amd64 + arm64) manifest lists. --- .github/workflows/ci.yml | 144 ++++++++++++++++++++--- .github/workflows/docker-tag.yml | 4 +- .github/workflows/registry-cleanup.yml | 8 +- .gitignore | 2 + Dockerfile | 82 ++++++++++--- README.md | 10 ++ build | 155 +++++++++++++++++++++---- 7 files changed, 345 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b036d16..328669e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,29 @@ name: "Build" on: - push: - branches: - - master pull_request: branches: - master - schedule: - - cron: '0 0 * * 0' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: - build_push_check: - name: Build docker image, publish it and run vuln scanner against it + build_amd64: + name: Build amd64 images (push by digest) permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - packages: write # for image publication to GitHub Packages - runs-on: ubuntu-latest + contents: read + packages: write + runs-on: ubuntu-24.04 environment: name: ci-build + outputs: + latest_image_tag: ${{ steps.build.outputs.LATEST_IMAGE_TAG }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 - name: Set up Docker Buildx - id: buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # 4.0.0 - name: Login to ghcr.io uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # 4.1.0 @@ -45,20 +44,129 @@ jobs: run: ./build --test - name: Describe images run: ./build --describe >> $GITHUB_STEP_SUMMARY - - name: Push images + - name: Push images by digest + env: + ORACLE_JAVA8_TOKEN: ${{ secrets.ORACLE_JAVA8_TOKEN }} run: ./build --push - - name: Run Trivy vulnerability scanner + - name: Upload digest metadata + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: digests-amd64 + path: digests/amd64-*.json + if-no-files-found: error + retention-days: 1 + + build_arm64: + name: Build arm64 images (push by digest) + permissions: + contents: read + packages: write + runs-on: ubuntu-24.04-arm + environment: + name: ci-build + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # 4.0.0 + - name: Login to ghcr.io + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # 4.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + docker-images: false + - name: Build arm64 images + env: + ORACLE_JAVA8_TOKEN: ${{ secrets.ORACLE_JAVA8_TOKEN }} + PLATFORM: linux/arm64 + run: ./build + - name: Test arm64 images + env: + PLATFORM: linux/arm64 + run: ./build --test + - name: Describe arm64 images + env: + PLATFORM: linux/arm64 + run: ./build --describe >> $GITHUB_STEP_SUMMARY + - name: Push arm64 images by digest + env: + ORACLE_JAVA8_TOKEN: ${{ secrets.ORACLE_JAVA8_TOKEN }} + PLATFORM: linux/arm64 + run: ./build --push + - name: Upload digest metadata + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: digests-arm64 + path: digests/arm64-*.json + if-no-files-found: error + retention-days: 1 + + merge_manifests: + name: Merge per-arch digests into multi-arch manifests + needs: [build_amd64, build_arm64] + permissions: + contents: read + security-events: write + packages: write + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # 4.0.0 + - name: Login to ghcr.io + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # 4.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Download amd64 digests + uses: actions/download-artifact@d3f86a106a0bac45b6d427b53961df85b0c84066 # v4.3.0 + with: + name: digests-amd64 + path: digests + - name: Download arm64 digests + uses: actions/download-artifact@d3f86a106a0bac45b6d427b53961df85b0c84066 # v4.3.0 + with: + name: digests-arm64 + path: digests + - name: Create multi-arch manifests + run: ./build --merge + - name: Run Trivy vulnerability scanner (amd64) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: '${{ needs.build_amd64.outputs.latest_image_tag }}' + format: 'sarif' + output: 'trivy-results-amd64.sarif' + severity: 'CRITICAL,HIGH' + limit-severities-for-sarif: true + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db + TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db + TRIVY_PLATFORM: linux/amd64 + - name: Upload amd64 Trivy results + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + with: + sarif_file: 'trivy-results-amd64.sarif' + category: trivy-amd64 + - name: Run Trivy vulnerability scanner (arm64) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: - image-ref: '${{ steps.build.outputs.LATEST_IMAGE_TAG }}' + image-ref: '${{ needs.build_amd64.outputs.latest_image_tag }}' format: 'sarif' - output: 'trivy-results.sarif' + output: 'trivy-results-arm64.sarif' severity: 'CRITICAL,HIGH' limit-severities-for-sarif: true env: TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db - - name: Upload Trivy scan results to GitHub Security tab + TRIVY_PLATFORM: linux/arm64 + - name: Upload arm64 Trivy results uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: - sarif_file: 'trivy-results.sarif' + sarif_file: 'trivy-results-arm64.sarif' + category: trivy-arm64 diff --git a/.github/workflows/docker-tag.yml b/.github/workflows/docker-tag.yml index d0ce300..2752be8 100644 --- a/.github/workflows/docker-tag.yml +++ b/.github/workflows/docker-tag.yml @@ -14,7 +14,7 @@ on: jobs: tag-images: name: Tag new images version - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write @@ -28,6 +28,8 @@ jobs: tar -xzf crane.tar.gz crane sudo mv crane /usr/local/bin/crane rm crane.tar.gz + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # 4.0.0 - name: Login to ghcr.io uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # 4.1.0 with: diff --git a/.github/workflows/registry-cleanup.yml b/.github/workflows/registry-cleanup.yml index f2a59c2..e6af9e8 100644 --- a/.github/workflows/registry-cleanup.yml +++ b/.github/workflows/registry-cleanup.yml @@ -12,7 +12,7 @@ jobs: contents: read packages: write steps: - - name: Prune untagged images + - name: Prune stale PR test image tags uses: vlaurin/action-ghcr-prune@0cf7d39f88546edd31965acba78cdcb0be14d641 #v0.6.0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -21,4 +21,8 @@ jobs: keep-younger-than: 30 # days prune-tags-regexes: | ^[a-z0-9]+_merge- - prune-untagged: true + # IMPORTANT: do NOT enable prune-untagged. With multi-arch manifest + # lists, each per-arch child manifest appears "untagged" in GHCR even + # though it is referenced by a tagged manifest list. Pruning untagged + # images would dereference the manifest lists and break pulls. + prune-untagged: false diff --git a/.gitignore b/.gitignore index 485dee6..c1b0fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .idea +.DS_Store +digests/ diff --git a/Dockerfile b/Dockerfile index 1ba7551..adf0565 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,20 @@ -# syntax=docker/dockerfile:1.6 - +# syntax=docker/dockerfile:1.10 + +# NOTE: This Dockerfile is intended to be invoked via the `./build` script, +# which sets ALL_JDK_STAGE / FULL_STAGE according to the target architecture. +# The defaults below are for amd64. When building arm64 directly with `docker +# build`, you MUST pass `--build-arg ALL_JDK_STAGE=all-jdk-arm64 --build-arg +# FULL_STAGE=full-arm64` (and `--platform linux/arm64`), or the produced image +# will silently mix amd64 binaries (zulu7, ibm8) into an arm64 image. ARG LATEST_VERSION +ARG ALL_JDK_STAGE=all-jdk-amd64 +ARG FULL_STAGE=full-amd64 FROM eclipse-temurin:${LATEST_VERSION}-jdk-noble AS temurin-latest # Intermediate image used to prune cruft from JDKs and squash them all. -FROM ubuntu:24.04 AS all-jdk +FROM ubuntu:24.04 AS all-jdk-common ARG LATEST_VERSION +ARG TARGETARCH RUN <<-EOT set -eux @@ -54,12 +63,9 @@ COPY --from=eclipse-temurin:25-jdk-noble /opt/java/openjdk /usr/lib/jvm/25 COPY --from=eclipse-temurin:26-jdk-noble /opt/java/openjdk /usr/lib/jvm/26 COPY --from=temurin-latest /opt/java/openjdk /usr/lib/jvm/${LATEST_VERSION} -COPY --from=azul/zulu-openjdk:7 /usr/lib/jvm/zulu7 /usr/lib/jvm/7 COPY --from=azul/zulu-openjdk:8 /usr/lib/jvm/zulu8 /usr/lib/jvm/zulu8 COPY --from=azul/zulu-openjdk:11 /usr/lib/jvm/zulu11 /usr/lib/jvm/zulu11 -COPY --from=ibmjava:8-sdk /opt/ibm/java /usr/lib/jvm/ibm8 - COPY --from=ibm-semeru-runtimes:open-8-jdk-noble /opt/java/openjdk /usr/lib/jvm/semeru8 COPY --from=ibm-semeru-runtimes:open-11-jdk-noble /opt/java/openjdk /usr/lib/jvm/semeru11 COPY --from=ibm-semeru-runtimes:open-17-jdk-noble /opt/java/openjdk /usr/lib/jvm/semeru17 @@ -76,9 +82,14 @@ COPY --from=ghcr.io/graalvm/native-image-community:25-ol10 /usr/lib64/graalvm/gr # 2. Once created, token should be added to GitHub protected environment by repository administrator. RUN --mount=type=secret,id=oracle_java8_token,uid=1001,gid=1001,mode=0400 <<-EOT set -eux + case "${TARGETARCH}" in + amd64) ORACLE_JAVA8_PACKAGE="jdk-8-linux-x64_bin.tar.gz" ;; + arm64) ORACLE_JAVA8_PACKAGE="jdk-8-linux-aarch64_bin.tar.gz" ;; + *) echo "Unsupported TARGETARCH for Oracle Java 8: ${TARGETARCH}" >&2; exit 1 ;; + esac sudo mkdir -p /usr/lib/jvm/oracle8 ORACLE_JAVA8_TOKEN="$(cat /run/secrets/oracle_java8_token)" - sudo curl -L --fail -H "token:${ORACLE_JAVA8_TOKEN}" https://java.oraclecloud.com/java/8/latest/jdk-8-linux-x64_bin.tar.gz | sudo tar -xvzf - -C /usr/lib/jvm/oracle8 --strip-components 1 + sudo curl -L --fail -H "token:${ORACLE_JAVA8_TOKEN}" "https://java.oraclecloud.com/java/8/latest/${ORACLE_JAVA8_PACKAGE}" | sudo tar -xvzf - -C /usr/lib/jvm/oracle8 --strip-components 1 unset ORACLE_JAVA8_TOKEN EOT @@ -91,6 +102,25 @@ RUN <<-EOT /usr/lib/jvm/graalvm*/lib/installer EOT +FROM --platform=linux/amd64 azul/zulu-openjdk:7 AS zulu7-amd64 +FROM --platform=linux/amd64 ibmjava:8-sdk AS ibm8-amd64 + +FROM all-jdk-common AS all-jdk-arm64 + +FROM all-jdk-common AS all-jdk-amd64 + +COPY --from=zulu7-amd64 /usr/lib/jvm/zulu7 /usr/lib/jvm/7 +COPY --from=ibm8-amd64 /opt/ibm/java /usr/lib/jvm/ibm8 + +RUN <<-EOT + sudo rm -rf \ + /usr/lib/jvm/*/lib/src.zip \ + /usr/lib/jvm/*/demo \ + /usr/lib/jvm/*/sample +EOT + +FROM ${ALL_JDK_STAGE} AS all-jdk + FROM scratch AS default-jdk ARG LATEST_VERSION @@ -107,6 +137,7 @@ COPY --from=all-jdk /usr/lib/jvm/${LATEST_VERSION} /usr/lib/jvm/${LATEST_VERSION FROM ubuntu:24.04 AS base ARG LATEST_VERSION ARG DATADOG_CI_VERSION=5.17.0 +ARG TARGETARCH ENV LATEST_VERSION=${LATEST_VERSION} # https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package @@ -163,13 +194,25 @@ RUN <<-EOT sudo pip3 install --break-system-packages awscli sudo pip3 cache purge + case "${TARGETARCH}" in + amd64) + DATADOG_CI_ARCH="x64" + VAULT_ARCH="amd64" + ;; + arm64) + DATADOG_CI_ARCH="arm64" + VAULT_ARCH="arm64" + ;; + *) echo "Unsupported TARGETARCH for tooling: ${TARGETARCH}" >&2; exit 1 ;; + esac + # datadog-ci - sudo curl -L --fail "https://github.com/DataDog/datadog-ci/releases/download/v${DATADOG_CI_VERSION}/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" + sudo curl -L --fail "https://github.com/DataDog/datadog-ci/releases/download/v${DATADOG_CI_VERSION}/datadog-ci_linux-${DATADOG_CI_ARCH}" --output "/usr/local/bin/datadog-ci" sudo chmod +x /usr/local/bin/datadog-ci # vault installation inspired by https://github.com/DataDog/datadog-agent-buildimages/blob/main/agent-deploy/Dockerfile VAULT_VERSION=1.20.4 - curl -fsSL "https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip" -o vault.zip + curl -fsSL "https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${VAULT_ARCH}.zip" -o vault.zip unzip vault.zip sudo mv vault /usr/local/bin/vault chmod +x /usr/local/bin/vault @@ -210,16 +253,14 @@ ENV JAVA_${VARIANT_UPPER}_HOME=/usr/lib/jvm/${VARIANT_LOWER} ENV JAVA_${VARIANT_LOWER}_HOME=/usr/lib/jvm/${VARIANT_LOWER} # Full image for debugging, contains all JDKs. -FROM base AS full +FROM base AS full-common USER non-root-user WORKDIR /home/non-root-user -COPY --from=all-jdk /usr/lib/jvm/7 /usr/lib/jvm/7 COPY --from=all-jdk /usr/lib/jvm/zulu8 /usr/lib/jvm/zulu8 COPY --from=all-jdk /usr/lib/jvm/zulu11 /usr/lib/jvm/zulu11 COPY --from=all-jdk /usr/lib/jvm/oracle8 /usr/lib/jvm/oracle8 -COPY --from=all-jdk /usr/lib/jvm/ibm8 /usr/lib/jvm/ibm8 COPY --from=all-jdk /usr/lib/jvm/semeru8 /usr/lib/jvm/semeru8 COPY --from=all-jdk /usr/lib/jvm/semeru11 /usr/lib/jvm/semeru11 COPY --from=all-jdk /usr/lib/jvm/semeru17 /usr/lib/jvm/semeru17 @@ -227,15 +268,11 @@ COPY --from=all-jdk /usr/lib/jvm/graalvm17 /usr/lib/jvm/graalvm17 COPY --from=all-jdk /usr/lib/jvm/graalvm21 /usr/lib/jvm/graalvm21 COPY --from=all-jdk /usr/lib/jvm/graalvm25 /usr/lib/jvm/graalvm25 -ENV JAVA_7_HOME=/usr/lib/jvm/7 - -ENV JAVA_ZULU7_HOME=/usr/lib/jvm/7 ENV JAVA_ZULU8_HOME=/usr/lib/jvm/zulu8 ENV JAVA_ZULU11_HOME=/usr/lib/jvm/zulu11 ENV JAVA_ORACLE8_HOME=/usr/lib/jvm/oracle8 -ENV JAVA_IBM8_HOME=/usr/lib/jvm/ibm8 # Temporarily set these aliases for backwards compatibility. ENV JAVA_IBM11_HOME=/usr/lib/jvm/semeru11 ENV JAVA_IBM17_HOME=/usr/lib/jvm/semeru17 @@ -247,3 +284,16 @@ ENV JAVA_SEMERU17_HOME=/usr/lib/jvm/semeru17 ENV JAVA_GRAALVM17_HOME=/usr/lib/jvm/graalvm17 ENV JAVA_GRAALVM21_HOME=/usr/lib/jvm/graalvm21 ENV JAVA_GRAALVM25_HOME=/usr/lib/jvm/graalvm25 + +FROM full-common AS full-arm64 + +FROM full-common AS full-amd64 + +COPY --from=all-jdk /usr/lib/jvm/7 /usr/lib/jvm/7 +COPY --from=all-jdk /usr/lib/jvm/ibm8 /usr/lib/jvm/ibm8 + +ENV JAVA_7_HOME=/usr/lib/jvm/7 +ENV JAVA_ZULU7_HOME=/usr/lib/jvm/7 +ENV JAVA_IBM8_HOME=/usr/lib/jvm/ibm8 + +FROM ${FULL_STAGE} AS full diff --git a/README.md b/README.md index 21689d9..bfaa148 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Image variants are available on a per JDK basis: - The `zulu8`, `zulu11`, `oracle8`, `ibm8`, `semeru8`, `semeru11`, `semeru17`, `graalvm17`, `graalvm21`, and `graalvm25` variants each contain the base JDKs in addition to the specific JDK from their name. - The `latest` variant contains the base JDKs and all of the specific JDKs above. +All variants are published as multi-arch manifests covering `linux/amd64` and `linux/arm64`, so the same tag (e.g. `base`, `zulu8`, `tip`) resolves to the correct image for the host architecture. The `7` and `ibm8` variants are amd64-only because the upstream JDK images are not available for arm64; `docker pull` for those tags on arm64 will fail. + Images are tagged with `ci-` prefixes via the [Tag new images version](https://github.com/DataDog/dd-trace-java-docker-build/actions/workflows/docker-tag.yml) workflow, which runs quarterly on `master` and when manually triggered. A **48-hour cooldown** is enforced: the workflow verifies that all external upstream dependencies (Eclipse Temurin, Azul Zulu, IBM Semeru, GraalVM, etc.) referenced in the Dockerfile were built at least 48 hours ago before tagging. This ensures that upstream images have had sufficient time for vulnerability scans and community review before being CI use. On completion, it automatically triggers the [Update mirror digests for ci-* images](https://github.com/DataDog/dd-trace-java-docker-build/actions/workflows/update-mirror-digests.yml) workflow, which opens a PR in [DataDog/images](https://github.com/DataDog/images) updating the pinned `ci-*` mirror image digests. Once that PR is merged, `dd-trace-java` CI picks up the updated images from `registry.ddbuild.io`. Images are mirrored in `registry.ddbuild.io` to ensure they are signed before use in CI. ## Development @@ -21,6 +23,14 @@ To build all the Docker images: ./build ``` +To build the arm64 Docker images: + +```bash +PLATFORM=linux/arm64 ./build +``` + +On an amd64 host this requires QEMU emulation set up for Docker (`docker run --privileged --rm tonistiigi/binfmt --install all` if not already configured); arm64 hosts build natively. + And then check the built images: ```bash diff --git a/build b/build index 77233c8..5f75504 100755 --- a/build +++ b/build @@ -5,7 +5,7 @@ readonly IMAGE_NAME="ghcr.io/datadog/dd-trace-java-docker-build" readonly BASE_VARIANTS=(8 11 17 21 25 tip) -readonly VARIANTS=( +readonly ALL_VARIANTS=( 7 zulu8 zulu11 @@ -19,11 +19,42 @@ readonly VARIANTS=( graalvm25 ) +# Variants whose upstream JDK images are not published for arm64. +readonly AMD64_ONLY_VARIANTS=(7 ibm8) + readonly LATEST_VERSION="26" +readonly PLATFORM="${PLATFORM:-linux/amd64}" +readonly DIGESTS_DIR="${DIGESTS_DIR:-./digests}" # Use buildkit to match CI as closely as possible. export DOCKER_BUILDKIT=1 +case "${PLATFORM}" in + linux/amd64) + readonly ARCH="amd64" + readonly ALL_JDK_STAGE="all-jdk-amd64" + readonly FULL_STAGE="full-amd64" + VARIANTS=("${ALL_VARIANTS[@]}") + ;; + linux/arm64) + readonly ARCH="arm64" + readonly ALL_JDK_STAGE="all-jdk-arm64" + readonly FULL_STAGE="full-arm64" + VARIANTS=() + for v in "${ALL_VARIANTS[@]}"; do + skip=false + for e in "${AMD64_ONLY_VARIANTS[@]}"; do + [[ "$v" == "$e" ]] && skip=true && break + done + $skip || VARIANTS+=("$v") + done + ;; + *) + echo "Unsupported platform: ${PLATFORM}" >&2 + exit 1 + ;; +esac + function compute_metadata() { # Get a TAG_PREFIX for every built image. Resulting images will be tagged as # ${TAG_PREFIX}${variant}, where ${variant} is base, latest or a JVM name. @@ -47,7 +78,10 @@ function compute_metadata() { GIT_HEAD_REF="$(git show-ref --head --hash ^HEAD)" } -# docker build wrapper with common arguments +# docker buildx wrapper for local single-platform builds. Uses --load so the +# resulting image is available in the local docker daemon for testing. Uses +# buildx (not plain `docker build`) so the buildx builder cache is shared with +# the later push-by-digest step. # See https://github.com/opencontainers/image-spec/blob/main/annotations.md for common labels # See https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package function docker_build() { @@ -55,15 +89,41 @@ function docker_build() { local tag="$2" shift shift - docker build \ + docker buildx build \ --build-arg LATEST_VERSION=$LATEST_VERSION \ + --build-arg ALL_JDK_STAGE="$ALL_JDK_STAGE" \ + --build-arg FULL_STAGE="$FULL_STAGE" \ --secret id=oracle_java8_token,env=ORACLE_JAVA8_TOKEN \ - --platform linux/amd64 \ + --platform "$PLATFORM" \ --label org.opencontainers.image.created="$BUILD_DATE" \ --label org.opencontainers.image.source=https://github.com/DataDog/dd-trace-java-docker-build \ --label org.opencontainers.image.revision="$GIT_HEAD_REF" \ --target "$target" \ --tag "$tag" \ + --load \ + "$@" \ + . +} + +# docker buildx wrapper that pushes the image by digest only (no tag) and writes +# the resulting manifest digest to a metadata file. The merge step consumes +# these files to assemble the multi-arch manifest list. +function buildx_push_by_digest() { + local target="$1" + local variant="$2" + shift 2 + docker buildx build \ + --build-arg LATEST_VERSION=$LATEST_VERSION \ + --build-arg ALL_JDK_STAGE="$ALL_JDK_STAGE" \ + --build-arg FULL_STAGE="$FULL_STAGE" \ + --secret id=oracle_java8_token,env=ORACLE_JAVA8_TOKEN \ + --platform "$PLATFORM" \ + --label org.opencontainers.image.created="$BUILD_DATE" \ + --label org.opencontainers.image.source=https://github.com/DataDog/dd-trace-java-docker-build \ + --label org.opencontainers.image.revision="$GIT_HEAD_REF" \ + --target "$target" \ + --output "type=image,name=${IMAGE_NAME},push-by-digest=true,name-canonical=true,push=true" \ + --metadata-file "${DIGESTS_DIR}/${ARCH}-${variant}.json" \ "$@" \ . } @@ -124,6 +184,8 @@ function do_test() { image_variant="$(image_name "${variant}")" echo "Running smoke tests for ${image_variant}..." docker run \ + --platform "$PLATFORM" \ + --env "PLATFORM=$PLATFORM" \ --user "$(id -u):$(id -g)" \ --workdir /work \ --volume "$(pwd):/work" \ @@ -164,6 +226,8 @@ function do_describe() { local image image="$(image_name latest)" docker run \ + --platform "$PLATFORM" \ + --env "PLATFORM=$PLATFORM" \ --user "$(id -u):$(id -g)" \ --workdir /work \ --volume "$(pwd):/work" \ @@ -206,32 +270,75 @@ function do_inner_describe() { echo } +# Push each built variant to the registry by digest (no tag) and write the +# resulting digest to ${DIGESTS_DIR}/${ARCH}-.json. The merge step +# combines these per-arch digests into multi-arch manifest tags. function do_push() { - local tag compute_metadata - for tag in base latest "${BASE_VARIANTS[@]}" "${VARIANTS[@]}"; do - tag="${tag,,}" - tag="$(image_name "${tag}")" - docker push "$tag" + mkdir -p "${DIGESTS_DIR}" + + buildx_push_by_digest base base + buildx_push_by_digest full latest + + # Base variants are aliases for the base image — same content, so reuse + # the base digest metadata. + for variant in "${BASE_VARIANTS[@]}"; do + cp "${DIGESTS_DIR}/${ARCH}-base.json" "${DIGESTS_DIR}/${ARCH}-${variant}.json" + done + + for variant in "${VARIANTS[@]}"; do + variant_upper="${variant^^}" + variant_lower="${variant,,}" + buildx_push_by_digest variant "${variant_lower}" \ + --build-arg "VARIANT_UPPER=$variant_upper" \ + --build-arg "VARIANT_LOWER=$variant_lower" + done +} + +# Combine per-arch digests written by --push into multi-arch manifest tags. +# Reads ${DIGESTS_DIR}/{amd64,arm64}-.json and creates +# ${IMAGE_NAME}:${TAG_PREFIX} referencing both architectures (or just +# one, for amd64-only variants like 7 and ibm8). +function do_merge() { + compute_metadata + local variant tag sources digest meta amd64_meta arm64_meta + + for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do + variant="${variant,,}" + tag="$(image_name "${variant}")" + sources=() + + amd64_meta="${DIGESTS_DIR}/amd64-${variant}.json" + arm64_meta="${DIGESTS_DIR}/arm64-${variant}.json" + + for meta in "${amd64_meta}" "${arm64_meta}"; do + if [[ -f "${meta}" ]]; then + digest="$(jq -r '."containerimage.digest"' "${meta}")" + sources+=("${IMAGE_NAME}@${digest}") + fi + done + + if [[ ${#sources[@]} -eq 0 ]]; then + echo "WARN: no digests for ${variant}, skipping" >&2 + continue + fi + + echo "Creating manifest ${tag} from ${#sources[@]} arch(es)" + docker buildx imagetools create --tag "${tag}" "${sources[@]}" done } +# Re-tag the current canonical multi-arch manifests with a ci- prefix by +# copying the manifest list (preserves both architectures). function do_tag() { - local tag + local variant src dst TAG_PREFIX= - echo "Pulling latest images" - for tag in base latest "${BASE_VARIANTS[@]}" "${VARIANTS[@]}"; do - tag="${tag,,}" - tag="$(image_name "${tag}")" - docker pull "$tag" - done - echo "Tagging ci- images" - for tag in base latest "${BASE_VARIANTS[@]}" "${VARIANTS[@]}"; do - tag="${tag,,}" - tag="$(image_name "${tag}")" - new_tag="${tag/:/:ci-}" - docker tag "$tag" "$new_tag" - docker push "$new_tag" + for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do + variant="${variant,,}" + src="${IMAGE_NAME}:${variant}" + dst="${IMAGE_NAME}:ci-${variant}" + echo "Copying ${src} -> ${dst}" + docker buildx imagetools create --tag "${dst}" "${src}" done } @@ -248,6 +355,8 @@ elif [[ ${1} = "--inner-describe" ]]; then do_inner_describe elif [[ ${1} = "--push" ]]; then do_push +elif [[ ${1} = "--merge" ]]; then + do_merge elif [[ ${1} = "--tag" ]]; then do_tag fi From cbca49a06f77cecfaf10812b5334925d9669f337 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 20 May 2026 11:17:16 -0400 Subject: [PATCH 2/7] Fixed download-artifact SHA --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 328669e..483b44a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,12 +125,12 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Download amd64 digests - uses: actions/download-artifact@d3f86a106a0bac45b6d427b53961df85b0c84066 # v4.3.0 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: digests-amd64 path: digests - name: Download arm64 digests - uses: actions/download-artifact@d3f86a106a0bac45b6d427b53961df85b0c84066 # v4.3.0 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: digests-arm64 path: digests From 06dc6254f2162a35a6ceb11eb33525f2cfeaee11 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 20 May 2026 16:11:40 -0400 Subject: [PATCH 3/7] Fixed review comments. --- .github/workflows/ci.yml | 7 ++++++- .github/workflows/docker-tag.yml | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 483b44a..caae962 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,18 @@ name: "Build" on: + push: + branches: + - master pull_request: branches: - master + schedule: + - cron: '0 0 * * 0' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false + cancel-in-progress: true jobs: build_amd64: diff --git a/.github/workflows/docker-tag.yml b/.github/workflows/docker-tag.yml index 2752be8..cf77b3a 100644 --- a/.github/workflows/docker-tag.yml +++ b/.github/workflows/docker-tag.yml @@ -49,7 +49,11 @@ jobs: # Collect all image references from COPY --from= and FROM directives mapfile -t ALL_REFS < <({ sed -n 's/.*--from=\([^ ]*\).*/\1/p' Dockerfile - awk '/^FROM/{print $2}' Dockerfile + awk '/^FROM/ { + for (i = 2; i <= NF; i++) { + if ($i !~ /^--/) { print $i; break } + } + }' Dockerfile }) # Filter to only external images From b111102d6bdfdef800d9d4562a3f7bd091fffeec Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Thu, 21 May 2026 16:55:18 -0400 Subject: [PATCH 4/7] Fixed review comments. --- .github/workflows/vuln-check.yml | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vuln-check.yml b/.github/workflows/vuln-check.yml index 02aad6b..898c38a 100644 --- a/.github/workflows/vuln-check.yml +++ b/.github/workflows/vuln-check.yml @@ -29,20 +29,42 @@ jobs: with: docker-images: false # Do not remove locally built images (including trivy scanner) - - name: Run Trivy vulnerability scanner + - name: Run Trivy vulnerability scanner (amd64) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: 'ghcr.io/datadog/dd-trace-java-docker-build:latest' format: 'sarif' - output: 'trivy-results.sarif' + output: 'trivy-results-amd64.sarif' severity: 'CRITICAL,HIGH' limit-severities-for-sarif: true env: TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db + TRIVY_PLATFORM: linux/amd64 - - name: Upload Trivy scan results to GitHub Security tab + - name: Upload amd64 Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 if: always() with: - sarif_file: 'trivy-results.sarif' + sarif_file: 'trivy-results-amd64.sarif' + category: trivy-amd64 + + - name: Run Trivy vulnerability scanner (arm64) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: 'ghcr.io/datadog/dd-trace-java-docker-build:latest' + format: 'sarif' + output: 'trivy-results-arm64.sarif' + severity: 'CRITICAL,HIGH' + limit-severities-for-sarif: true + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db + TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db + TRIVY_PLATFORM: linux/arm64 + + - name: Upload arm64 Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + if: always() + with: + sarif_file: 'trivy-results-arm64.sarif' + category: trivy-arm64 From 4b036f7ac429428ddb395a15faaf5b9a91d8fe8f Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Fri, 22 May 2026 17:55:51 +0200 Subject: [PATCH 5/7] chore: Apply suggestions from code review Fixes workflow's `cancel-in-progress`. Fixes useless variable TAG_PREFIX in function. Fixes image-ref workflow step output. Co-authored-by: Sarah Chen Co-authored-by: Brice Dutheil --- .github/workflows/ci.yml | 4 ++-- build | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caae962..e4ee914 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: build_amd64: @@ -161,7 +161,7 @@ jobs: - name: Run Trivy vulnerability scanner (arm64) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: - image-ref: '${{ needs.build_amd64.outputs.latest_image_tag }}' + image-ref: '${{ needs.build_arm64.outputs.latest_image_tag }}' format: 'sarif' output: 'trivy-results-arm64.sarif' severity: 'CRITICAL,HIGH' diff --git a/build b/build index 5f75504..5811caa 100755 --- a/build +++ b/build @@ -332,7 +332,9 @@ function do_merge() { # copying the manifest list (preserves both architectures). function do_tag() { local variant src dst - TAG_PREFIX= +function do_tag() { + local variant src dst + for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do variant="${variant,,}" src="${IMAGE_NAME}:${variant}" From 13c7da8e893657537321ee061d696592a34fcb17 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Fri, 22 May 2026 13:06:08 -0400 Subject: [PATCH 6/7] Fixed typo. --- build | 3 --- 1 file changed, 3 deletions(-) diff --git a/build b/build index 5811caa..7354241 100755 --- a/build +++ b/build @@ -332,9 +332,6 @@ function do_merge() { # copying the manifest list (preserves both architectures). function do_tag() { local variant src dst -function do_tag() { - local variant src dst - for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do for variant in base latest "${BASE_VARIANTS[@]}" "${ALL_VARIANTS[@]}"; do variant="${variant,,}" src="${IMAGE_NAME}:${variant}" From 8b8114e1bc92ae578c158487cff2eb00cf92a0a6 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Fri, 22 May 2026 13:53:40 -0400 Subject: [PATCH 7/7] Fixed failed `Post Run Trivy vulnerability scanner (arm64)`. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4ee914..ea65267 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,8 @@ jobs: runs-on: ubuntu-24.04-arm environment: name: ci-build + outputs: + latest_image_tag: ${{ steps.build.outputs.LATEST_IMAGE_TAG }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 @@ -85,6 +87,7 @@ jobs: with: docker-images: false - name: Build arm64 images + id: build env: ORACLE_JAVA8_TOKEN: ${{ secrets.ORACLE_JAVA8_TOKEN }} PLATFORM: linux/arm64