Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# VRX 4 development image
# ROS 2 Lyrical Luth (Ubuntu 26.04 "Resolute") + Gazebo Jetty.
#
# Unlike the legacy image, this does NOT fake the gz vendor packages or build
# ros_gz from source. VRX 4's gz_waves packages `find_package(gz_sim_vendor)`,
# so we install the REAL ros-<distro>-gz-*-vendor packages (they carry the CMake
# shims and pull in Gazebo Jetty). This image only provides the environment;
# build the VRX workspace at runtime in the mounted ~/vrx_ws.
FROM ros:lyrical-ros-base AS vrx-base

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC

# ---- Locale + base tooling ------------------------------------------------
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl gnupg lsb-release locales tzdata \
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone \
&& locale-gen en_US en_US.UTF-8 \
&& update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \
&& rm -rf /var/lib/apt/lists/*
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8

# ---- OSRF Gazebo apt repo -------------------------------------------------
# Provides Gazebo Jetty and the libs the gz_*_vendor packages depend on.
RUN curl -s https://packages.osrfoundation.org/gazebo.gpg \
-o /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/gazebo-stable.list

# ---- Gazebo Jetty + ROS gz vendor shims + ros_gz --------------------------
# The gz-*-vendor packages provide the CMake shims that VRX 4's gz_waves
# packages find_package(). The quoted glob is passed literally to apt, which
# expands it to every ros-<distro>-gz-<x>-vendor package (msgs, transport,
# fuel-tools, sim, rendering, ...). Gazebo itself arrives via these vendor
# packages + ros-gz; Resolute (26.04) has no standalone `gz-jetty` metapackage
# in the OSRF repo, and installing one alongside the vendors would be redundant.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-ros-gz \
"ros-${ROS_DISTRO}-gz-*-vendor" \
ros-${ROS_DISTRO}-sdformat-vendor \
&& rm -rf /var/lib/apt/lists/*

# ---- Standard ROS packages + build tooling VRX uses -----------------------
# NOTE: nav2 (navigation2, nav2-bringup, nav2-minimal-*) is intentionally
# omitted — it is often not yet released early in a new distro's life. Add
# ros-${ROS_DISTRO}-navigation2 etc. once available on Lyrical.
RUN apt-get update \

@caguero caguero Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This list can drift from our package.xml files over time. Maybe generate it with rosdep or note rosdep as the source of truth.

&& apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-ackermann-msgs \
ros-${ROS_DISTRO}-actuator-msgs \
ros-${ROS_DISTRO}-ament-cmake-pycodestyle \
ros-${ROS_DISTRO}-image-transport \
ros-${ROS_DISTRO}-image-transport-plugins \
ros-${ROS_DISTRO}-joy-teleop \
ros-${ROS_DISTRO}-joy-linux \
ros-${ROS_DISTRO}-mavros-msgs \
ros-${ROS_DISTRO}-radar-msgs \
ros-${ROS_DISTRO}-vision-msgs \
ros-${ROS_DISTRO}-xacro \
python3-colcon-common-extensions \
python3-rosdep \
python3-vcstool \
&& rm -rf /var/lib/apt/lists/*

# ---- Dev tools + NVIDIA GL (rocker --nvidia) ------------------------------
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
atop expect gdb gnutls-bin iputils-ping libbluetooth-dev libcwiid-dev \
net-tools python3-dbg python3-pip python3-venv vim wget xvfb \
&& dpkg --add-architecture i386 \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need the i386 GL libs? Nothing in VRX is 32 bit, so this block could probably go.

&& apt-get update \
&& apt-get install -y --no-install-recommends \
libxau6 libxau6:i386 libxdmcp6 libxdmcp6:i386 libxcb1 libxcb1:i386 \
libxext6 libxext6:i386 libx11-6 libx11-6:i386 libglvnd0 libglvnd0:i386 \
libgl1 libgl1:i386 libglx0 libglx0:i386 libegl1 libegl1:i386 \
libgles2 libgles2:i386 \
&& rm -rf /var/lib/apt/lists/*

# ---- Source ROS for interactive + non-interactive shells ------------------
RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" > /etc/profile.d/ros.sh \
&& chmod +x /etc/profile.d/ros.sh \
&& echo "source /etc/profile.d/ros.sh" >> /etc/bash.bashrc
ENV BASH_ENV=/etc/profile.d/ros.sh

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heads up: BASH_ENV makes every noninteractive bash source the ROS setup, which can surprise scripts that use set -u.


WORKDIR /ws
CMD ["/bin/bash"]
49 changes: 49 additions & 0 deletions docker/build.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash

#
# Copyright (C) 2018 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#

# Builds a Docker image.
# Usage: build.bash <dir containing Dockerfile> [tag]
# tag defaults to the resolved directory name, so passing '.' works too.
#
# For the VRX dev container, build it as vrx_dev:lyrical so it matches the
# image referenced in compose.yaml:
# ./build.bash <dir containing Dockerfile> lyrical
image_name=vrx_dev
Comment thread
bsb808 marked this conversation as resolved.

if [ $# -lt 1 ]
then
echo "Usage: $0 <path to directory containing Dockerfile> [tag]"
exit 1
fi

context="$1"
if [ ! -f "${context}/Dockerfile" ]
then
echo "Err: ${context} does not contain a Dockerfile to build."
exit 1
fi

# Tag: explicit 2nd arg, else the resolved directory basename (handles '.').
distro="${2:-$(basename "$(cd "${context}" && pwd)")}"

image_plus_tag=$image_name:$(export LC_ALL=C; date +%Y_%m_%d_%H%M)
docker build --rm -t "$image_plus_tag" -f "${context}/Dockerfile" "${context}" && \
docker tag "$image_plus_tag" "$image_name:$distro" && \
echo "Built $image_plus_tag and tagged as $image_name:$distro" && \
echo "To run: ./docker/run_compose.bash"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Missing newline at end of file.

29 changes: 29 additions & 0 deletions docker/compose.nvidia.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# NVIDIA overlay for the VRX 4 dev container.
#
# run_compose.bash layers this on top of compose.yaml ONLY when it detects an
# NVIDIA GPU on the host, so the base stays portable (Intel/AMD iGPU via
# /dev/dri) and NVIDIA hosts render on the dGPU via PRIME offload.
#
# Manual use: docker compose -f docker/compose.yaml -f docker/compose.nvidia.yaml run --rm dev
services:
dev:
# nvidia-container-toolkit injects the driver libs + /dev/nvidia* when these
# are set and the reservation below requests the GPU.
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
# PRIME render offload: on a hybrid/Optimus host the X display is driven by
# the iGPU and NVIDIA is the offload GPU. /dev/dri (base) is the present path.
- __NV_PRIME_RENDER_OFFLOAD=1
- __GLX_VENDOR_LIBRARY_NAME=nvidia
- __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/10_nvidia.json

# Modern Compose + nvidia-container-toolkit. If your Docker still uses the
# legacy runtime, replace this block with `runtime: nvidia`.
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
65 changes: 65 additions & 0 deletions docker/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Dev container for VRX 4 (ROS 2 Lyrical + Gazebo Jetty), via plain Docker
# Compose so it does NOT depend on rocker knowing Ubuntu 26.04.
#
# Mirrors: rocker --home --user --x11 --nvidia --devices /dev/input/js0 ... bash
#
# Easiest: ./docker/run_compose.bash
#
# Manually:
# xhost +SI:localuser:$(id -un) # let the container reach your X server (once per login)
# cd ~/WorkingCopies/oceansim/vrx_ws/src/vrx

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This personal path is still here. Maybe something generic like cd <your vrx_ws>/src/vrx.

# MY_UID=$(id -u) MY_GID=$(id -g) docker compose -f docker/compose.yaml run --rm --remove-orphans dev
# ( `UID` is a bash readonly var, so we pass MY_UID/MY_GID instead. )
#
# (`run --rm` gives a fresh interactive shell and removes the container on exit,
# which is the rocker-like behaviour. Use `exec` instead if you prefer a
# long-lived container: `docker compose ... up -d` then `... exec dev bash`.)

services:
dev:
image: vrx_dev:lyrical # <- the tag you built; adjust if different

# --- interactive shell (rocker's default) ---
stdin_open: true # docker -i
tty: true # docker -t
command: bash

# --- run as your host user so files created in the mounted home keep your
# ownership (rocker --user). UID/GID come from the run command; the
# prompt may read "I have no name!" — cosmetic, since this UID has no
# /etc/passwd entry in the image. Drop this line to run as root. ---
user: "${MY_UID:-1000}:${MY_GID:-1000}"

# --- host networking: ROS 2 DDS discovery + simplest X11 (DISPLAY reaches
# the host X server directly) ---
network_mode: host
ipc: host

# Workspace root. run_compose.bash derives VRX_WS from the compose file's
# location; the default (current directory) covers a bare `docker compose ...`
# invocation, dropping you in wherever you're standing.
working_dir: ${VRX_WS:-${PWD}}

environment:
- DISPLAY=${DISPLAY}
- HOME=${HOME}
- QT_X11_NO_MITSHM=1
# This base runs on the Intel/AMD iGPU via the /dev/dri passthrough below.
# NVIDIA access + PRIME render offload live in compose.nvidia.yaml, which
# run_compose.bash layers in automatically when an NVIDIA GPU is detected.

volumes:
- ${HOME}:${HOME} # rocker --home
Comment thread
bsb808 marked this conversation as resolved.
- /tmp/.X11-unix:/tmp/.X11-unix:rw # X11 socket
# No .Xauthority mount: run_compose.bash's `xhost +SI:localuser:$(id -un)`
# grants local access without it, and GDM's $XAUTHORITY
# (/run/user/.../gdm/Xauthority) collides with the home mount if
# bind-mounted onto ~/.Xauthority.

devices:
# - /dev/input/js0:/dev/input/js0 # joystick (delete if absent)
- /dev/dri:/dev/dri # direct rendering (Intel/AMD/GL)

# --- GPU: the NVIDIA reservation + PRIME offload are in compose.nvidia.yaml,
# layered in by run_compose.bash only when an NVIDIA GPU is present.
# This base alone renders on the Intel/AMD iGPU via /dev/dri above. ---
45 changes: 45 additions & 0 deletions docker/run_compose.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash

@caguero caguero Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

build.bash carries the Apache header but this script does not. Should we add it for consistentcy?

#
# Minimal launcher for the VRX 4 dev container via Docker Compose
# (ROS 2 Lyrical + Gazebo Jetty). Mirrors rocker: interactive shell, home mount,
# GPU, X11, joystick. Service definition lives in docker/compose.yaml.
#
# Usage:
# ./docker/run_compose.bash # interactive bash in the container
# ./docker/run_compose.bash <cmd...> # run a command instead of bash
#
set -euo pipefail

# Resolve compose.yaml next to this script, so it works from any directory.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Workspace root = three levels up from docker/ (…/<ws>/src/vrx/docker).
# Derived from the script's own location so the repo can live anywhere; the
# compose file falls back to a default if this isn't exported.
export VRX_WS
VRX_WS="$(cd "${SCRIPT_DIR}/../../.." && pwd)"

# Let the container's (host-UID) process reach the host X server. Harmless to
# re-run; warn but don't abort if there's no X server (e.g. headless).
xhost +SI:localuser:"$(id -un)" >/dev/null 2>&1 \
|| echo "WARN: xhost failed (no X server?); GUI apps may not display." >&2

# Host UID/GID so files created in the mounted home keep your ownership.
# (`UID` is a bash readonly variable, hence the MY_ prefix the compose expects.)
export MY_UID MY_GID
MY_UID="$(id -u)"
MY_GID="$(id -g)"

# GPU selection: prefer the NVIDIA dGPU when one is present and usable on the
# host, otherwise fall back to the Intel/AMD iGPU (base compose only). The
# NVIDIA reservation refuses to start the container on a host without an NVIDIA
# GPU, so only layer the overlay in when nvidia-smi actually reports one.
compose_files=(-f "${SCRIPT_DIR}/compose.yaml")
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the driver is present but the nvidia container toolkit is not, compose fails to start instead of falling back to the iGPU. Maybe also check docker info for the nvidia runtime.

compose_files+=(-f "${SCRIPT_DIR}/compose.nvidia.yaml")
echo "GPU: NVIDIA detected -> dGPU via PRIME offload." >&2
else
echo "GPU: no usable NVIDIA -> Intel/AMD iGPU via /dev/dri." >&2
fi

exec docker compose "${compose_files[@]}" run --rm --remove-orphans dev "$@"
41 changes: 21 additions & 20 deletions gz_waves_rendering/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,28 @@ find_package(gz-rendering REQUIRED COMPONENTS ogre2)
# registry (CreateWaveSimulation); the GUI-side factory registration lives in
# the per-engine GUI plugins (gz-sim-waves-<engine>-gui), loaded by the world.

# Discover Ogre Next headers. Different Gazebo installs sit on different Ogre
# Next patch versions (ROS gz_ogre_next_vendor 2.3.3 vs Ubuntu libogre-next-dev
# 2.3.1). Compiling against one but loading the other silently swaps in slow
# Hlms/barrier code paths (no crash, but RTF tanks), so probe which Ogre Next
# the *resolved* gz-rendering-ogre2 links against and match our header path.
get_target_property(_gz_rend_ogre2_lib gz-rendering::ogre2 LOCATION)
if(_gz_rend_ogre2_lib MATCHES "/opt/ros/([^/]+)/")
set(_ros_distro "${CMAKE_MATCH_1}")
set(OGRE_NEXT_INCLUDE_DIR
"/opt/ros/${_ros_distro}/opt/gz_ogre_next_vendor/include/OGRE-Next"
CACHE PATH "Ogre Next include dir matched to gz-rendering-ogre2 distro")
else()
# Non-ROS-distro install (e.g. local jetty_ws build) → system Ogre Next.
set(OGRE_NEXT_INCLUDE_DIR "/usr/include/OGRE-2.3"
CACHE PATH "Ogre Next include dir from libogre-next-dev")
endif()
if(NOT EXISTS "${OGRE_NEXT_INCLUDE_DIR}/OgreRoot.h")
# Discover the Ogre Next headers. We must compile against the SAME Ogre Next the
# resolved gz-rendering-ogre2 uses (mixing patch versions silently swaps in slow
# Hlms/barrier paths). The previous approach parsed gz-rendering::ogre2's
# LOCATION, but that property is empty for imported/interface targets (it is
# NOTFOUND on the ROS gz vendor + Gazebo Jetty), so it wrongly fell back to a
# hardcoded /usr/include/OGRE-2.3. Instead, search for the OGRE-Next headers,
# seeded by the ogre2 target's own interface include dirs. PATH_SUFFIXES exclude
# a bare "OGRE" so we never pick up classic Ogre 1.x (/usr/include/OGRE,
# rviz_ogre_vendor), whose ABI-incompatible headers would break the build.
get_target_property(_ogre2_incs gz-rendering::ogre2 INTERFACE_INCLUDE_DIRECTORIES)
find_path(OGRE_NEXT_INCLUDE_DIR
NAMES OgreRoot.h
HINTS ${_ogre2_incs}
PATHS
/opt/ros/$ENV{ROS_DISTRO}/opt/gz_ogre_next_vendor/include
/usr/include
PATH_SUFFIXES OGRE-Next OGRE-2.4 OGRE-2.3
)
Comment on lines +41 to +48

@caguero caguero Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

find_path checks the standard system paths before these PATHS, so on a host with both libogre-next-dev and gz_ogre_next_vendor it can pick the mismatched /usr/include copy and silently reintroduce the slow Hlms path. Consider a first pass with NO_DEFAULT_PATH so the vendor headers win, then a plain fallback.

if(NOT OGRE_NEXT_INCLUDE_DIR)

@caguero caguero Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

find_path reuses a stale cached value even if the path no longer exists, so keeping the old EXISTS check on OgreRoot.h would still catch that case.

message(FATAL_ERROR
"OgreRoot.h not found under ${OGRE_NEXT_INCLUDE_DIR} "
"(probed from gz-rendering::ogre2 = ${_gz_rend_ogre2_lib}). "
"Install libogre-next-dev or gz_ogre_next_vendor as appropriate.")
"OGRE-Next headers (OgreRoot.h) not found. Install libogre-next-dev or the "
"gz_ogre_next_vendor package.")
endif()
message(STATUS "Ogre Next headers: ${OGRE_NEXT_INCLUDE_DIR}")

Expand Down