Phase 0: A GPU, a container, and a window on Windows
Goal: one command builds a robotics environment, and it renders on the GPU, inside Docker, inside WSL, on a Windows laptop.
Picking a stack (in 2026)
Chris last used ROS 2 a while ago. It's still the standard, so we chose:
| Layer | Choice | Why |
|---|---|---|
| Middleware | ROS 2 Jazzy (LTS to 2029) | Same Ubuntu (24.04) as the WSL host |
| Simulator | Gazebo Harmonic | Jazzy's paired release, good for outdoor ground robots |
| Observability | Elasticsearch + Kibana 9.5.3 | Chris loves Kibana dashboards |
| Everything else | Docker Compose + Makefile | Repeatable, pinned, scriptable by an agent |
We passed on Isaac Sim for now. It's gorgeous, but heavy, it isn't officially supported on WSL, and 12 GB of VRAM is tight.
The interesting trick: OpenGL through WSL
CUDA in Docker on WSL is easy (gpus: all). OpenGL rendering is the subtle part: Gazebo needs a real GPU, or it silently falls back to slow CPU rendering (llvmpipe). The recipe:
devices: [/dev/dxg] # WSL's GPU paravirtualization device
volumes: [/usr/lib/wsl:/usr/lib/wsl:ro] # Windows driver userspace libs
environment:
GALLIUM_DRIVER: d3d12 # Mesa -> Direct3D 12 -> NVIDIA
MESA_D3D12_DEFAULT_ADAPTER_NAME: NVIDIA
And make gpu-check proves it:
NVIDIA GeForce RTX 4080 Laptop GPU
OpenGL renderer string: D3D12 (NVIDIA GeForce RTX 4080 Laptop GPU)
OpenGL core profile version string: 4.6 (Core Profile) Mesa 25.2.8
What broke
gz: command not found. Non-interactive shells skip.bashrc, so ROS was never sourced. Fixed with a tinyscripts/in_ros.shwrapper that everymaketarget goes through.- Kibana "unavailable". It's slow to start (about a minute of migrations), and with no healthcheck
make upreturned too early. We added a real healthcheck. - Kibana encryption keys were randomly generated on every start, which breaks saved objects across restarts. We pinned local-only keys.
- Claude wrote a broken Makefile line (a
bash -cquoting trick that would have launched an empty shell), caught it on review, and replaced it with the wrapper script. Pair programming goes both ways.
Result
make doctor ✔ 6/6 host checks
make gpu-check ✔ CUDA + OpenGL 4.6 on the 4080
make sim-smoke ✔ 2000 physics steps, headless
make es-check ✔ ES green, Kibana available
Lessons learned
- Pin the base image by digest, not by tag. Tags move; digests don't.
- A health check that isn't checked (
--waitwith no healthcheck) is just optimism.