Skip to main content

Docker Environment

The Docker container is the alternative to the native pixi workflow. There are two ways to use it:

  1. Dev Container (VS Code) -- Open the repo in VS Code and choose Reopen in Container.
  2. Standalone container (docker-compose) -- Run simulations on any host without VS Code. NVIDIA GPU passthrough is enabled automatically when hardware is detected.

Both methods use the same sim image built from docker/Dockerfile.

Dockerfile

The Dockerfile at docker/Dockerfile produces a single stage named sim built on Ubuntu 24.04. It is divided into labelled sections:

ubuntu:24.04 AS sim
├── BASE - locale, user setup, libdrm stub
├── VSG - VulkanSceneGraph stack (built from source, /opt/vsg)
├── CHRONO - Project Chrono (built from source, /home/trickfire/chrono)
├── GAZEBO - ROS 2 Jazzy + Gazebo Harmonic
├── VNC/DISPLAY - headless GUI stack
└── DEV TOOLING - SSH server, ruff, pre-commit, shfmt

A BUILD_JOBS build arg controls parallelism for the C++ builds (defaults to nproc).

VSG section

VulkanSceneGraph and its dependencies are built from source and installed to /opt/vsg:

LibraryVersionPurpose
glslang16.1.0GLSL shader compiler (runtime library)
KTX-Softwarev4.4.2Texture format support
draco1.5.7Mesh compression
assimpv6.0.53D asset import
VulkanSceneGraph (vsg)v1.1.15Vulkan-based scene graph rendering
vsgXchangev1.1.12Asset format bridge for vsg (uses assimp)
vsgImGuiv0.7.0ImGui integration for vsg overlays

LD_LIBRARY_PATH is set to /opt/vsg/lib so the shared libraries are found at runtime.

Chrono section

Project Chrono is cloned from source and built with the Vehicle and VSG modules enabled:

CH_ENABLE_MODULE_VEHICLE - wheel/terrain dynamics
CH_ENABLE_MODULE_VSG - Vulkan-based visualizer

The build target is demo_VEH_SCMTerrain_RigidTire. An upstream bug in SCMTerrain.cpp (domain list not cleared before AddActiveDomain) is patched at image build time.

The Chrono source and build live at /home/trickfire/chrono and are owned by the trickfire user.

Gazebo section

Simulation stack:

PackagePurpose
Gazebo HarmonicPhysics simulation engine
ros-jazzy-ros-gzROS 2 / Gazebo integration + bridge
ros-jazzy-ros2-controllersJoint state broadcaster + trajectory controller
ros-jazzy-gz-ros2-controlHardware interface for Gazebo
ros-jazzy-rviz2Robot visualization
ros-jazzy-xacroURDF macro processing
ros-jazzy-joint-state-publisher-guiJoint state publishing GUI

Python packages: pypresence, trimesh, pyfqmr

VNC / display section

Display stack (headless GUI):

PackagePurpose
xserver-xorg-core + xserver-xorg-video-dummyVirtual X server with dummy driver (no GPU)
openboxLightweight window manager
x11vncVNC server for remote X11 access
novnc + websockifyBrowser-based VNC client
xvfbVirtual framebuffer for Jetson/Tegra (no DRI)
mesa-utils, libgl1-mesa-driSoftware OpenGL rendering
kmodKernel module tools (for /lib/modules mount)
x11-appsX11 utilities including xeyes

Dev tooling section

Package/toolPurpose
openssh-serverSSH server (used by VS Code remote connection)
ruffPython linter/formatter
pre-commitGit hook framework
shfmtShell script formatter

Container user

The container runs as a non-root user trickfire with passwordless sudo:

RUN useradd trickfire --uid 1000 --shell /bin/bash --create-home --no-log-init
RUN echo "trickfire ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/user

Dev Container configuration

The devcontainer.json configures how VS Code opens the container:

Build target: sim stage of docker/Dockerfile

Workspace mount: The repo is bind-mounted into the container at /home/trickfire/simulations.

Compose config: .devcontainer/devcontainer.json uses docker/docker-compose.yml plus docker/docker-compose-dev.yml. The override adds Wayland and display environment variables for Vulkan rendering.

Port forwarding: Ports 6080 (noVNC) and 5900 (VNC) are forwarded to the host.

Privileged mode: The container runs with --privileged and device access for hardware interaction (USB, CAN bus).

VS Code extensions: Pre-installs Python, C++ (clangd, CMake, Makefile), ROS, URDF, Docker, Prettier, and other formatting extensions.

X-server Display Architecture

Xorg/Xvfb → x11vnc → websockify → Browser
↑ ↑
Openbox WM noVNC client
↑ (port 6080)
Gazebo / RViz / Chrono VSG

Why this approach?

Alternatives like Xvfb don't support GLX properly, which means Gazebo's 3D rendering fails. Using Xorg with a dummy driver + Mesa software rendering gives us full OpenGL support without needing a real GPU. The VNC + noVNC layer makes it accessible from any browser. On Jetsons, we fall back to Xvfb because there's no /dev/dri, but GPU rendering still works via EGL through injected Tegra libs.

Chrono uses Vulkan for rendering via the VSG module. Wayland socket passthrough (WAYLAND_DISPLAY, XDG_RUNTIME_DIR) is exposed to the container so Vulkan can reach the host compositor when available.

Environment variables

VariableValueSet in
DISPLAYHost-dependentdocker-compose.yml (standalone)
WAYLAND_DISPLAYHost-dependentdocker-compose-dev.yml
XDG_RUNTIME_DIR/run/host-runtimedocker-compose-dev.yml
VNC_PORT5900Dockerfile
NOVNC_PORT6080Dockerfile
VSG_INSTALL_DIR/opt/vsgDockerfile
LD_LIBRARY_PATH/opt/vsg/libDockerfile

Extending the container

To add system packages, edit docker/Dockerfile and rebuild. For Python packages, add them to the pip3 install line in the Gazebo section (simulation-time) or the dev tooling section (development-only tools).

After changing the Dockerfile, use Dev Containers: Rebuild Container in VS Code's Command Palette.