Skip to content

Improve incremental container builds#295

Draft
aristarkhovNV wants to merge 17 commits intomainfrom
aaristarkhov/containers
Draft

Improve incremental container builds#295
aristarkhovNV wants to merge 17 commits intomainfrom
aaristarkhov/containers

Conversation

@aristarkhovNV
Copy link
Copy Markdown
Collaborator

@aristarkhovNV aristarkhovNV commented Mar 19, 2026

Summary by CodeRabbit

  • Chores
    • Added a Docker ignore to restrict Docker build context.
    • Switched test container builds to be driven by Docker Compose and updated related run scripts.
    • Updated CI Docker build steps to use the build-push-action with caching and buildx bumped.
    • Reduced builder image tooling to shrink footprint.
    • Enabled dependency install caching during image builds.
    • Clarified versioning output to distinguish CI vs local builds.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new .dockerignore, shifts CloudXR test image builds into Docker Compose and scripts, tweaks Dockerfiles (order and packages, BuildKit cache usage), updates GitHub Actions to use buildx action with cache, and makes CMake git-based version discovery conditional on CI vs local builds.

Changes

Cohort / File(s) Summary
Docker context & compose
\.dockerignore, deps/cloudxr/docker-compose.test.yaml, scripts/run_tests_with_cloudxr.sh
New .dockerignore excludes all by default and re-includes required repo paths for CloudXR builds. Compose test service now includes a build block using ${CXR_BUILD_CONTEXT} with dockerfile and args. Test orchestration script replaced direct docker build with docker compose ... build and adjusted subsequent compose up invocation.
CloudXR test Dockerfile
deps/cloudxr/Dockerfile.test
Moved COPY install/ /app/install/ after test files copy; removed a redundant WORKDIR /app/tests before the final CMD.
ROS2 example Dockerfile
examples/teleop_ros2/Dockerfile
Removed clang-format and git from builder apt install list; added BuildKit cache mounts and UV_CACHE_DIR usage for Python dependency steps.
CI build action
.github/workflows/build-ubuntu.yml
Replaced inline docker build step with docker/build-push-action@v7 (buildx), specifying context, file, build args, tagging, and cache-from/cache-to; bumped docker/setup-buildx-action to v4.0.0.
CI-aware versioning
cmake/IsaacTeleopVersion.cmake
Git discovery and git commands now run only when CI is truthy. Local builds use defaults (git count 0, Python version suffixed with +local, build kind local); CI preserves prior git-derived versioning logic and adjusts final status messages.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Dev / Local
  participant CI as CI Runner
  participant GH as GitHub Actions (build-push-action)
  participant Script as run_tests_with_cloudxr.sh
  participant Compose as Docker Compose
  participant Docker as Docker Daemon
  Note over Dev,CI: Two possible flows

  Dev->>Script: invoke script (uses docker compose build)
  Script->>Compose: docker compose build (cloudxr-runtime, isaacteleop-tests) with CXR_BUILD_CONTEXT
  Compose->>Docker: build images from repository context (uses .dockerignore)
  Script->>Compose: docker compose up -d cloudxr-runtime
  Compose->>Docker: create & start runtime container

  CI->>GH: workflow triggers teleop-ros2 build
  GH->>Docker: build image via build-push-action (buildx, cache, load:true)
  GH->>Docker: load built image into runner
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I tuck files away, then peek what to send,
Compose builds the mixers, scripts call them to play,
CI counts the hops while local keeps a friend,
Docker trims its tools and caches bright as day,
A little hop, a test — and off the changes sway.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve incremental container builds' directly aligns with the main changes across the pull request, which focus on optimizing Docker build performance through caching, .dockerignore configuration, and Docker Compose integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aaristarkhov/containers

Comment @coderabbitai help to get the list of available commands and usage tips.

@aristarkhovNV aristarkhovNV force-pushed the aaristarkhov/containers branch from 0017afc to d3ed299 Compare March 30, 2026 18:50
@aristarkhovNV aristarkhovNV marked this pull request as ready for review March 30, 2026 18:54
Copilot AI review requested due to automatic review settings March 30, 2026 18:54
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.dockerignore:
- Around line 1-21: Remove the redundant allow-pattern and its comment: delete
the line containing "!deps/cloudxr/" (and the preceding comment "Allow CloudXR
Dockerfiles and runtime assets." if present) because the later pattern "!deps/"
already re-includes the entire deps/ tree; update .dockerignore so only the
broader "!deps/" entry remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 814d1fe0-6df0-4ab9-9af6-1959df615145

📥 Commits

Reviewing files that changed from the base of the PR and between 74796ec and d3ed299.

📒 Files selected for processing (6)
  • .dockerignore
  • cmake/IsaacTeleopVersion.cmake
  • deps/cloudxr/Dockerfile.test
  • deps/cloudxr/docker-compose.test.yaml
  • examples/teleop_ros2/Dockerfile
  • scripts/run_tests_with_cloudxr.sh
💤 Files with no reviewable changes (1)
  • examples/teleop_ros2/Dockerfile

Comment on lines +1 to +21
# Keep Docker build contexts lean for CloudXR compose builds.
# Ignore everything by default, then allow only what current Dockerfiles need.

*

# Allow Docker metadata files.
!.dockerignore

# Allow CloudXR Dockerfiles and runtime assets.
!deps/cloudxr/

# Allow build artifacts and tests consumed by Dockerfile.test and Dockerfile.runtime.
!install/
!examples/

# Allow building from source for ros2 workflows
!cmake/
!deps/
!src/
!CMakeLists.txt
!VERSION
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Redundant pattern at line 10.

Line 10 (!deps/cloudxr/) is redundant because line 18 (!deps/) already re-includes the entire deps/ directory. Consider removing line 10 and its associated comment.

🔧 Suggested fix
 # Allow Docker metadata files.
 !.dockerignore

-# Allow CloudXR Dockerfiles and runtime assets.
-!deps/cloudxr/
-
 # Allow build artifacts and tests consumed by Dockerfile.test and Dockerfile.runtime.
 !install/
 !examples/
 
-# Allow building from source for ros2 workflows
+# Allow CloudXR Dockerfiles, runtime assets, and building from source for ros2 workflows
 !cmake/
 !deps/
 !src/
 !CMakeLists.txt
 !VERSION
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.dockerignore around lines 1 - 21, Remove the redundant allow-pattern and
its comment: delete the line containing "!deps/cloudxr/" (and the preceding
comment "Allow CloudXR Dockerfiles and runtime assets." if present) because the
later pattern "!deps/" already re-includes the entire deps/ tree; update
.dockerignore so only the broader "!deps/" entry remains.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves incremental container builds for CloudXR test runs by moving container image builds under Docker Compose (so cache and service build definitions are shared), and by reducing build-context churn with a restrictive root .dockerignore. It also adjusts CMake version derivation to avoid requiring Git metadata for non-CI builds (which is important when Docker build contexts exclude .git).

Changes:

  • Switch run_tests_with_cloudxr.sh from docker build + docker compose up --build to a dedicated docker compose build step and up without rebuilds.
  • Add a Compose build: definition for the isaacteleop-tests service and reorder COPY steps in the test Dockerfile to improve layer caching.
  • Add a root .dockerignore to keep the CloudXR compose build context lean, and relax CMake version logic to only require Git when running under CI.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/run_tests_with_cloudxr.sh Builds runtime + test images via docker compose build and avoids rebuilding on up, improving incremental iteration.
examples/teleop_ros2/Dockerfile Removes unused build packages (clang-format, git) from the ROS2 image build stage.
deps/cloudxr/docker-compose.test.yaml Adds build: config for isaacteleop-tests so Compose can build it directly from repo root context with args.
deps/cloudxr/Dockerfile.test Reorders COPY to improve cache reuse and removes redundant WORKDIR.
cmake/IsaacTeleopVersion.cmake Avoids requiring Git for non-CI version calculation, improving builds in contexts without .git.
.dockerignore Introduces an allowlist-based build context for root-context Docker builds (used by CloudXR compose builds).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
examples/teleop_ros2/Dockerfile (2)

74-87: ⚠️ Potential issue | 🟠 Major

rm -rf build defeats the cache mount's purpose for incremental builds.

The --mount=type=cache on line 74 persists the build directory across docker build invocations to enable incremental compilation. However, rm -rf build on line 87 destroys these cached artifacts at the end of each build, negating the incremental build benefit.

Additionally, cache mount contents are never included in Docker layers, so this removal doesn't reduce image size—it only wipes the cache.

Proposed fix: Remove the unnecessary rm -rf
     && cmake --build build --config Release -j$(nproc) \
-    && cmake --install build --config Release \
-    && rm -rf build
+    && cmake --install build --config Release
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/teleop_ros2/Dockerfile` around lines 74 - 87, The Dockerfile
currently mounts a build cache using
--mount=type=cache,target=/opt/isaacteleop/build,... to enable incremental
builds but then deletes that cache with rm -rf build after cmake install; remove
the rm -rf build step so the cache mount is preserved across builds (i.e.,
delete the final "rm -rf build" invocation following the cmake --install
command) and rely on the cache mount for cleanup/size management instead of
removing the build directory.

65-69: ⚠️ Potential issue | 🔴 Critical

Add git to the builder stage's apt-get install—FetchContent dependencies require it.

The CMake configuration (line 75-81) sets BUILD_PLUGINS=ON and uses FetchContent_Declare with GIT_REPOSITORY URLs to fetch sanitizers-cmake, openxr-sdk, yaml-cpp, flatbuffers, and mcap from GitHub. All of these are unconditional dependencies. Without git, the CMake configure step will fail.

Proposed fix
 RUN apt-get update && apt-get install -y --no-install-recommends \
     build-essential \
     cmake \
     pkg-config \
+    git \
     && rm -rf /var/lib/apt/lists/*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/teleop_ros2/Dockerfile` around lines 65 - 69, The builder stage
apt-get install in the Dockerfile is missing git, but the project's CMake uses
FetchContent_Declare (unconditional FetchContent dependencies like
sanitizers-cmake, openxr-sdk, yaml-cpp, flatbuffers, mcap) when BUILD_PLUGINS=ON
during configure; add git to the apt-get install list in the builder stage so
FetchContent can clone those GIT_REPOSITORY URLs and the CMake configure step
succeeds (update the RUN apt-get install -y --no-install-recommends ... line to
include git).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@examples/teleop_ros2/Dockerfile`:
- Around line 74-87: The Dockerfile currently mounts a build cache using
--mount=type=cache,target=/opt/isaacteleop/build,... to enable incremental
builds but then deletes that cache with rm -rf build after cmake install; remove
the rm -rf build step so the cache mount is preserved across builds (i.e.,
delete the final "rm -rf build" invocation following the cmake --install
command) and rely on the cache mount for cleanup/size management instead of
removing the build directory.
- Around line 65-69: The builder stage apt-get install in the Dockerfile is
missing git, but the project's CMake uses FetchContent_Declare (unconditional
FetchContent dependencies like sanitizers-cmake, openxr-sdk, yaml-cpp,
flatbuffers, mcap) when BUILD_PLUGINS=ON during configure; add git to the
apt-get install list in the builder stage so FetchContent can clone those
GIT_REPOSITORY URLs and the CMake configure step succeeds (update the RUN
apt-get install -y --no-install-recommends ... line to include git).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 06111663-9829-45d8-af7e-e57147ecdf1b

📥 Commits

Reviewing files that changed from the base of the PR and between a161b0b and c7973c4.

📒 Files selected for processing (1)
  • examples/teleop_ros2/Dockerfile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build-ubuntu.yml:
- Around line 171-182: The GitHub Action uses docker/build-push-action@v7 with
context: . and file: examples/teleop_ros2/Dockerfile, but your .dockerignore
currently unignores all of examples/, sending unnecessary files and killing
cache effectiveness; open .dockerignore and replace the broad unignore (e.g.,
"!examples/") with a narrow allowlist that only unignores the exact build
context and required files (for example "examples/teleop_ros2/**", plus any
specific top-level files the Dockerfile needs such as LICENSE or build scripts),
or alternatively change the action's context to examples/teleop_ros2 to avoid
sending the repo root—apply the approach that best fits the Dockerfile
dependencies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ac00f03-82b6-4297-92b4-d41a4f83fccf

📥 Commits

Reviewing files that changed from the base of the PR and between c7973c4 and c15d8fb.

📒 Files selected for processing (2)
  • .github/workflows/build-ubuntu.yml
  • examples/teleop_ros2/Dockerfile
💤 Files with no reviewable changes (1)
  • examples/teleop_ros2/Dockerfile

Comment on lines +171 to +182
uses: docker/build-push-action@v7
with:
context: .
file: examples/teleop_ros2/Dockerfile
build-args: |
ROS_DISTRO=${{ matrix.ros_distro }}
PYTHON_VERSION=${{ matrix.python_version }}
tags: teleop_ros2_ref:${{ matrix.ros_distro }}
load: true
push: false
cache-from: type=gha,scope=teleop-ros2-${{ matrix.ros_distro }}
cache-to: type=gha,mode=min,scope=teleop-ros2-${{ matrix.ros_distro }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

context: . currently defeats incremental gains due to overly broad .dockerignore allowlist.

Line 173 switches to repo-root context, but .dockerignore currently unignores all of examples/. That sends far more files than needed on every build and weakens cache efficiency.

Suggested fix (narrow allowlist in .dockerignore)
-!examples/
+!examples/
+# Re-ignore all examples content, then allow only required paths.
+examples/**
+!examples/teleop_ros2/
+!examples/teleop_ros2/**
+!examples/oxr/
+!examples/oxr/python/
+!examples/oxr/python/**
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-ubuntu.yml around lines 171 - 182, The GitHub Action
uses docker/build-push-action@v7 with context: . and file:
examples/teleop_ros2/Dockerfile, but your .dockerignore currently unignores all
of examples/, sending unnecessary files and killing cache effectiveness; open
.dockerignore and replace the broad unignore (e.g., "!examples/") with a narrow
allowlist that only unignores the exact build context and required files (for
example "examples/teleop_ros2/**", plus any specific top-level files the
Dockerfile needs such as LICENSE or build scripts), or alternatively change the
action's context to examples/teleop_ros2 to avoid sending the repo root—apply
the approach that best fits the Dockerfile dependencies.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/teleop_ros2/Dockerfile`:
- Around line 110-114: The Dockerfile repeats the inline assignment
UV_CACHE_DIR=/root/.cache/uv before each uv invocation (seen around the RUN that
calls "uv venv" and "uv pip install"); remove the redundant per-command prefixes
by either adding a single ENV UV_CACHE_DIR=/root/.cache/uv earlier in the
Dockerfile or by omitting UV_CACHE_DIR entirely and relying on uv's default
cache when running as root, then update the RUN line(s) to call "uv venv
--python=${PYTHON_VERSION}" and "uv pip install ..." without the inline
UV_CACHE_DIR=... prefixes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ee2cbcd8-c6a5-4700-abea-6d958d33ef4f

📥 Commits

Reviewing files that changed from the base of the PR and between c15d8fb and b0cc0d5.

📒 Files selected for processing (1)
  • examples/teleop_ros2/Dockerfile

Comment on lines 110 to 114
RUN --mount=type=cache,target=/root/.cache/uv,id=isaacteleop-teleop-ros2-uv-${PYTHON_VERSION} \
UV_CACHE_DIR=/root/.cache/uv uv venv --python=${PYTHON_VERSION} && \
UV_CACHE_DIR=/root/.cache/uv uv pip install setuptools wheel && \
UV_CACHE_DIR=/root/.cache/uv uv pip install --no-build-isolation-package nlopt \
-r /tmp/requirements.txt -r /tmp/requirements-retargeters.txt msgpack msgpack-numpy && \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider reducing verbosity by setting UV_CACHE_DIR once.

The inline UV_CACHE_DIR=/root/.cache/uv prefix is repeated for each uv command. Since /root/.cache/uv is already uv's default cache location when running as root, the explicit setting is defensive but redundant. If you prefer to keep it explicit for clarity, consider setting it once:

 RUN --mount=type=cache,target=/root/.cache/uv,id=isaacteleop-teleop-ros2-uv-${PYTHON_VERSION} \
-    UV_CACHE_DIR=/root/.cache/uv uv venv --python=${PYTHON_VERSION} && \
-    UV_CACHE_DIR=/root/.cache/uv uv pip install setuptools wheel && \
-    UV_CACHE_DIR=/root/.cache/uv uv pip install --no-build-isolation-package nlopt \
+    export UV_CACHE_DIR=/root/.cache/uv && \
+    uv venv --python=${PYTHON_VERSION} && \
+    uv pip install setuptools wheel && \
+    uv pip install --no-build-isolation-package nlopt \
         -r /tmp/requirements.txt -r /tmp/requirements-retargeters.txt msgpack msgpack-numpy && \
     rm /tmp/requirements.txt /tmp/requirements-retargeters.txt

Alternatively, since this is the default location, you could omit UV_CACHE_DIR entirely and rely on uv's default behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/teleop_ros2/Dockerfile` around lines 110 - 114, The Dockerfile
repeats the inline assignment UV_CACHE_DIR=/root/.cache/uv before each uv
invocation (seen around the RUN that calls "uv venv" and "uv pip install");
remove the redundant per-command prefixes by either adding a single ENV
UV_CACHE_DIR=/root/.cache/uv earlier in the Dockerfile or by omitting
UV_CACHE_DIR entirely and relying on uv's default cache when running as root,
then update the RUN line(s) to call "uv venv --python=${PYTHON_VERSION}" and "uv
pip install ..." without the inline UV_CACHE_DIR=... prefixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants