# Multi-stage build for the RealSense D4xx API server on aarch64 (Jetson) # and x86_64. The Intel apt repo ships librealsense2 runtime for arm64 # but no pyrealsense2 wheel, so the Python bindings are built from source # in a dedicated builder stage and copied into the runtime image. # --------------------------------------------------------------------------- # Stage 1: build pyrealsense2 (Python bindings) from source # --------------------------------------------------------------------------- FROM ubuntu:22.04 AS librealsense-builder ENV DEBIAN_FRONTEND=noninteractive # Pinned to v2.50.0: this is the version proven to work with the D435i # firmware 05.15.x shipped on the G1 robot. v2.55+ opens the device but # wait_for_frames times out (likely an interaction between the newer USB # backend and the older D4xx firmware). ARG RS_VERSION=v2.50.0 RUN apt-get update && apt-get install -y --no-install-recommends \ git cmake build-essential pkg-config ca-certificates \ libssl-dev libusb-1.0-0-dev libudev-dev \ python3 python3-dev python3-pip \ && rm -rf /var/lib/apt/lists/* WORKDIR /src RUN git clone --depth 1 --branch ${RS_VERSION} https://github.com/IntelRealSense/librealsense.git WORKDIR /src/librealsense/build RUN cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_EXAMPLES=OFF \ -DBUILD_GRAPHICAL_EXAMPLES=OFF \ -DBUILD_TOOLS=OFF \ -DBUILD_UNIT_TESTS=OFF \ -DBUILD_NETWORK_DEVICE=OFF \ -DBUILD_PYTHON_BINDINGS=ON \ -DPYTHON_EXECUTABLE=/usr/bin/python3 \ -DCMAKE_INSTALL_PREFIX=/opt/install \ && make -j$(nproc) \ && make install # Collect both the python bindings and the native runtime .so. We copy them # from the builder stage rather than apt-installing librealsense2 in the # runtime stage, so the .so version always matches the bindings. RUN mkdir -p /opt/pyrealsense2 /opt/librealsense-runtime \ && cp -r /usr/lib/python3/dist-packages/pyrealsense2/. /opt/pyrealsense2/ 2>/dev/null || true \ && find /src/librealsense/build -name 'pyrealsense2*.so' -exec cp {} /opt/pyrealsense2/ \; \ && find /src/librealsense/build -name 'librealsense2.so*' -exec cp -P {} /opt/librealsense-runtime/ \; \ && ( [ -f /opt/pyrealsense2/__init__.py ] || printf 'from .pyrealsense2 import *\n' > /opt/pyrealsense2/__init__.py ) \ && echo "=== /opt/pyrealsense2 ===" && ls -la /opt/pyrealsense2 \ && echo "=== /opt/librealsense-runtime ===" && ls -la /opt/librealsense-runtime # --------------------------------------------------------------------------- # Stage 2: runtime image # # Note: the React frontend is built on the host (npm in the Jetson's Docker # can't reach registry.npmjs.org — systemd-resolved stub at 127.0.0.53 # doesn't work inside containers). Build with: # docker run --rm --dns 8.8.8.8 -v $(pwd)/src/frontend/react:/app \ # -w /app node:22-alpine sh -c 'npm install && npm run build' # Then `docker compose build` just copies the prebuilt dist/. # --------------------------------------------------------------------------- FROM ubuntu:22.04 AS runtime ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ python3 python3-pip python3-venv \ libusb-1.0-0 libudev1 \ libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 \ usbutils \ && rm -rf /var/lib/apt/lists/* # librealsense native runtime — copied straight from the builder stage so # the .so version matches the python bindings exactly (see RS_VERSION above # for why we don't apt-install). COPY --from=librealsense-builder /opt/librealsense-runtime/ /usr/local/lib/ RUN ldconfig # Python bindings (built in stage 1). System Python 3.10 looks in # /usr/lib/python3/dist-packages, so install there. COPY --from=librealsense-builder /opt/pyrealsense2 /usr/lib/python3/dist-packages/pyrealsense2 WORKDIR /app # Python dependencies — keep this layer cacheable: install before copying source. RUN pip3 install --no-cache-dir \ "fastapi>=0.115" \ "uvicorn[standard]>=0.34" \ "pydantic>=2.0" \ "pydantic-settings>=2.0" \ "python-dotenv>=1.0" \ "nats-py>=2.9" \ "numpy>=1.24,<2" \ "opencv-python-headless>=4.8" # Application source COPY src/backend/ src/backend/ COPY src/frontend/react/dist/ src/frontend/react/dist/ # Entrypoint COPY scripts/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh # Optional .env (compose can also inject via env_file) COPY .env* ./ EXPOSE 8878 ENTRYPOINT ["/app/entrypoint.sh"]