From 99e7bc1d48b57bcd7e66f99386b6d28f707692dc Mon Sep 17 00:00:00 2001 From: hujc Date: Thu, 30 Jul 2026 22:12:37 -0700 Subject: [PATCH] [CI] Publish the kit-less image from the nightly cron (#6820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 1. Summary - Adds an `image` dimension to the nightly publish matrix so `docker/Dockerfile.kitless` is built and pushed alongside `docker/Dockerfile.base`. - Both images share the `isaaclab_image_name` repository and are distinguished by a `-kitless` tag suffix, so no registry-side provisioning is required. - Base image tags, platforms and build arguments are unchanged, verified by simulating every branch x image combination. - One file, +30 / -8. ## 2. Background The kit-less container landed on `develop` in https://github.com/isaac-sim/IsaacLab/pull/6355 — "[Newton] Add a kitless training container". That PR builds and validates the image in `kitless-docker.yml`, but publishing is owned by this workflow, which only ever built `Dockerfile.base`. As a result no kit-less image reaches the registry. Scheduled workflows run only from the default branch, which is `release/3.0.0-beta2` — every nightly since 2026-07-26 has run from it — so this change belongs here rather than on `main`. ## 3. Design - `CRON_BRANCHES` is unchanged; the matrix cross-product is branch x image. - The kit-less leg pins `linux/amd64`. `ubuntu:24.04` is multi-arch and `ovrtx` ships an aarch64 wheel, but no arm64 GPU runner validates that build, so the published manifest is limited to the architecture CI exercises. - A Dockerfile-existence guard lets a branch cut before an image was introduced skip it instead of failing the run. This is required because `Dockerfile.kitless` does not exist on this branch or on older releases. - `concurrency.group` gains the image key. Without it the two legs share a group under `cancel-in-progress: true` and cancel each other. - `DOCKER_USER_HOME_ARG` moves into the matrix (`/root` for base, `/home/isaaclab` for kit-less) so the published image matches its Compose profile. The `ISAACSIM_*` arguments stay inline because `strategy.matrix` cannot reference the `steps` context that supplies them. ## 4. Test plan - [x] Simulated the build step across `develop`, `release/3.0.0-beta2` and `main` x both images with a stubbed `docker`; confirmed tags, platforms, Dockerfile and `DOCKER_USER_HOME_ARG` per leg, and that base output is identical to today. - [x] Verified `main` is still skipped by both legs, as before. - [x] `uv run isaaclab -f` on all files. - [ ] First real publish is the next nightly run after merge. --- .github/workflows/publish-images.yaml | 72 +++++++++++++++++++++------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/.github/workflows/publish-images.yaml b/.github/workflows/publish-images.yaml index 30fe4f22772d..2dee4303e8db 100644 --- a/.github/workflows/publish-images.yaml +++ b/.github/workflows/publish-images.yaml @@ -54,7 +54,7 @@ jobs: echo "Resolved branches: $arr" build-and-push-images: - name: Build and Push (${{ matrix.branch }}) + name: Build and Push (${{ matrix.branch }} / ${{ matrix.image.key }}) needs: resolve-branches runs-on: [self-hosted, gpu] timeout-minutes: 180 @@ -62,8 +62,25 @@ jobs: fail-fast: false matrix: branch: ${{ fromJson(needs.resolve-branches.outputs.branches) }} + image: + - key: base + tag_suffix: "" + dockerfile: docker/Dockerfile.base + isaacsim_base: "true" + # Empty: follow whatever the Isaac Sim base image supports. + platforms: "" + build_args: --build-arg DOCKER_USER_HOME_ARG=/root + - key: kitless + # Same repository as the base image, distinguished by a tag suffix. + tag_suffix: "-kitless" + dockerfile: docker/Dockerfile.kitless + # Builds on ubuntu, so it takes none of the Isaac Sim build arguments. + isaacsim_base: "false" + # No arm64 GPU runner validates this build yet. + platforms: linux/amd64 + build_args: --build-arg DOCKER_USER_HOME_ARG=/home/isaaclab concurrency: - group: publish-images-${{ matrix.branch }} + group: publish-images-${{ matrix.branch }}-${{ matrix.image.key }} cancel-in-progress: true environment: name: postmerge-production @@ -133,6 +150,15 @@ jobs: run: | IMAGE_BASE_VERSION="${{ steps.config.outputs.isaacsim_image_tag }}" IMAGE="${{ steps.config.outputs.isaaclab_image_name }}" + DOCKERFILE="${{ matrix.image.dockerfile }}" + SUFFIX="${{ matrix.image.tag_suffix }}" + + # Not every publishable branch carries every Dockerfile. A branch cut + # before an image was introduced skips it instead of failing the run. + if [ ! -f "$DOCKERFILE" ]; then + echo "🟠 $DOCKERFILE does not exist on '$BRANCH_NAME'; nothing to publish." + exit 0 + fi # Use the SHA actually checked out for this branch (the matrix ref), not # github.sha — on a scheduled run github.sha is the default branch HEAD, @@ -161,14 +187,14 @@ jobs: case "$BRANCH_NAME" in develop) - MOVING_TAG="$IMAGE:latest-develop" - IMMUTABLE_TAG="$IMAGE:latest-develop-$FULL_SHA" + MOVING_TAG="$IMAGE:latest-develop$SUFFIX" + IMMUTABLE_TAG="$IMAGE:latest-develop-$FULL_SHA$SUFFIX" ;; release/*) # Sanitize the part after release/ for use as a Docker tag suffix. RELEASE_SUFFIX=$(echo "${BRANCH_NAME#release/}" | sed 's/[^a-zA-Z0-9._-]/-/g') - MOVING_TAG="$IMAGE:latest-release-$RELEASE_SUFFIX" - IMMUTABLE_TAG="$IMAGE:latest-release-$RELEASE_SUFFIX-$FULL_SHA" + MOVING_TAG="$IMAGE:latest-release-$RELEASE_SUFFIX$SUFFIX" + IMMUTABLE_TAG="$IMAGE:latest-release-$RELEASE_SUFFIX-$FULL_SHA$SUFFIX" ;; main) # main is intentionally not published here; see postmerge-ci.yml for main. @@ -195,8 +221,14 @@ jobs: echo "🔵 Checking if base image supports multiarch..." BASE_IMAGE_FULL="${{ steps.config.outputs.isaacsim_image_name }}:${IMAGE_BASE_VERSION}" ARCHITECTURES=$(docker manifest inspect "$BASE_IMAGE_FULL" 2>/dev/null | grep -o '"architecture": "[^"]*"' | cut -d'"' -f4 | sort -u) + EXPLICIT_PLATFORMS="${{ matrix.image.platforms }}" - if [ -z "$ARCHITECTURES" ]; then + if [ -n "$EXPLICIT_PLATFORMS" ]; then + # An image that does not build on the Isaac Sim base pins its own + # platforms; the probe above does not describe it. + BUILD_PLATFORMS="$EXPLICIT_PLATFORMS" + echo "Using explicit platforms: $BUILD_PLATFORMS" + elif [ -z "$ARCHITECTURES" ]; then echo "🟠 Could not inspect base image manifest: $BASE_IMAGE_FULL - defaulting to linux/amd64 only" BUILD_PLATFORMS="linux/amd64" else @@ -231,19 +263,29 @@ jobs: TAG_ARGS+=("-t" "$t") done + BUILD_ARG_FLAGS=( + --build-arg "ISAACLAB_PATH_ARG=/workspace/isaaclab" + ${{ matrix.image.build_args }} + ) + # Only the Isaac Sim-based image declares these; passing them to an image + # that does not leaves unconsumed build arguments in the build log. + if [ "${{ matrix.image.isaacsim_base }}" = "true" ]; then + BUILD_ARG_FLAGS+=( + --build-arg "ISAACSIM_BASE_IMAGE_ARG=${{ steps.config.outputs.isaacsim_image_name }}" + --build-arg "ISAACSIM_VERSION_ARG=$IMAGE_BASE_VERSION" + --build-arg "ISAACSIM_ROOT_PATH_ARG=/isaac-sim" + ) + fi + echo "🔵 Building and pushing image with tags listed above..." if ! docker buildx build \ --platform "$BUILD_PLATFORMS" \ --progress=plain \ "${TAG_ARGS[@]}" \ - --build-arg ISAACSIM_BASE_IMAGE_ARG=${{ steps.config.outputs.isaacsim_image_name }} \ - --build-arg ISAACSIM_VERSION_ARG="$IMAGE_BASE_VERSION" \ - --build-arg ISAACSIM_ROOT_PATH_ARG=/isaac-sim \ - --build-arg ISAACLAB_PATH_ARG=/workspace/isaaclab \ - --build-arg DOCKER_USER_HOME_ARG=/root \ - --cache-from type=gha \ - --cache-to type=gha,mode=max \ - -f docker/Dockerfile.base \ + "${BUILD_ARG_FLAGS[@]}" \ + --cache-from type=gha,scope=publish-${{ matrix.image.key }} \ + --cache-to type=gha,mode=max,scope=publish-${{ matrix.image.key }} \ + -f "$DOCKERFILE" \ --push .; then echo "🔴 docker buildx build/push failed for tags:" printf ' - %s\n' "${TAGS[@]}"