Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0af8eea
Adapt droid for Basis Franka hardware setup
amburger66 Jul 29, 2026
b51ccf3
Rewrite NUC container: panda-only, micromamba, locked env, no git/con…
yiyunliu Aug 18, 2026
9e1ed00
Bump fairo: restore conf/data symlinks dropped by the repo strip
yiyunliu Aug 18, 2026
ba1b792
Bump fairo: surface ControlUpdate exceptions; fix NDEBUG-elided metad…
yiyunliu Aug 18, 2026
1e67a8b
add quadlet files
yiyunliu Aug 18, 2026
4044920
Port live gripper behavior from the NUC worktree
yiyunliu Aug 18, 2026
659fab1
add selinux options
yiyunliu Aug 18, 2026
6f7b4ff
update submodule url
yiyunliu Aug 18, 2026
a5d59d7
make the image more portable
yiyunliu Aug 18, 2026
f502e60
Stop killing controllers from clients; connect lazily instead
yiyunliu Aug 20, 2026
0ccb261
push the gripper and robot launch inside the supervise_launch script
yiyunliu Aug 20, 2026
49eb376
make sure dead service is properly cleaned up
yiyunliu Aug 20, 2026
45ceb50
install nano
yiyunliu Aug 20, 2026
93b6983
Drive the robot IP from one place and reject malformed supervisor args
yiyunliu Aug 20, 2026
d2734ca
update pyproject.toml and add uv.lock file
yiyunliu Aug 21, 2026
9db5bfe
update the handling of the ROBOT_IP environment variable
yiyunliu Aug 24, 2026
b5b4515
push dm-robotics-* into optional dependencies
yiyunliu Aug 24, 2026
38173f2
comment out aruco for now
yiyunliu Aug 24, 2026
b4d8919
franka: expose motor_torques_external in get_robot_state
naiimic Sep 11, 2026
3872921
Merge pull request #1 from BasisResearch/expose-motor-torques-external
datvo06 Sep 11, 2026
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
60 changes: 45 additions & 15 deletions .docker/README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
# Overview

In order to simplify the setup and deployment of DROID across different machines we supply Dockerfiles for both the control server (nuc) and the client machine (laptop). The directory structure is broken down as follows:

├── nuc # directory for nuc docker setup files
├──── Dockerfile.nuc # nuc image definition
├──── docker-compose-nuc.yaml # nuc container deployment settings
├── laptop # directory for laptop docker setup files
This directory contains the Docker setups for the two machines on the platform: the control server (nuc) and the client machine (laptop).

├── nuc # control server (Franka Panda)
├──── Dockerfile.nuc # image definition
├──── docker-compose-nuc.yaml # runtime contract (mounts, networking, RT limits)
├──── launch_robot.sh # container replacement for droid/franka/launch_robot.sh
├──── launch_gripper.sh # container replacement for droid/franka/launch_gripper.sh
├──── droid-nuc.build # quadlet build unit (systemd-managed deployment)
├──── droid-nuc.container # quadlet container unit (systemd-managed deployment)
├── laptop # laptop docker setup files
├──── Dockerfile.laptop # laptop image definition
├──── docker-compose-laptop.yaml # laptop container deployment settings
├──── entrypoint.sh # script that is run on entrypoint of Docker container

We recognise that some users may not already be familiar with Docker, the syntax of Dockerfiles and the syntax of docker compose configuration files. We point the user towards the following resources on these topics:
# NUC control server

* [Docker Overview](https://docs.docker.com/get-started/overview/)
* [Dockerfile Reference](https://docs.docker.com/engine/reference/builder/)
* [Docker Compose Overview](https://docs.docker.com/compose/)
The NUC image targets the **Franka Panda only** (libfranka 0.9.0, pinned in the Dockerfile).

## What is baked into the image (build time)

* The droid codebase and the `droid/fairo` submodule, installed into the `polymetis-local` conda environment (managed by micromamba, whose solver resolves the environment in minutes rather than the hours classic conda takes).
* Compiled libfranka 0.9.0 and polymetis.
* The polymetis configuration — robot IP, controller gains, safety limits — which lives in the fairo submodule at `droid/fairo/polymetis/polymetis/conf/` (`robot_client/franka_hardware.yaml`, `robot_model/franka_panda.yaml`, `gripper/franka_hand.yaml`). To change these, edit them there and rebuild; the image is the single source of truth at runtime.
* Container-specific launch scripts from this directory, which replace the bare-metal pixi scripts in `droid/franka/` (those hardcode `/home/robot` paths and a pixi manifest that only exists on the physical NUC). The entrypoint itself is inlined in the Dockerfile: `tini` starts the zerorpc control server (`scripts/server/run_server.py`) in the `polymetis-local` conda environment.

Submodules must be checked out before building: `git submodule update --init --recursive`.

## What the container needs at runtime (docker-compose-nuc.yaml)

# NUC Setup
In order to set up the control server on your NUC run `sudo ./nuc_setup.sh` from this [directory](https://github.com/droid-dataset/droid/tree/main/scripts/setup). Running through all the steps in this script will install host system dependencies and ensure the control server runs automatically in a docker container each time your machine is booted.
* **Mount:** `droid/misc/parameters.py` → `/app/droid/misc/parameters.py`. Machine-specific parameters, read at runtime (the server uses `sudo_password` to launch the real-time controller processes). Mounted so it can be edited without a rebuild.
* **Host networking:** the laptop connects to the zerorpc server on port 4242 and to polymetis gRPC on 50051; libfranka connects out to the robot control box at its static IP.
* **Real-time scheduling:** `cap_add: SYS_NICE` plus `ulimits` `rtprio: 99` and `memlock: -1` — required for the 1 kHz libfranka control loop (SCHED_FIFO threads, and `mlockall` of the full libtorch-loaded process). The host kernel must be RT-patched (see `scripts/setup/nuc_setup.sh`).
* **Device:** `/dev/cpu_dma_latency`, which the RT setup writes to pin CPU C-states — the only device node the stack touches; the robot and gripper are network clients. No `privileged` needed.
* **`restart: always`** so the control server comes back up on boot.

Further details on the steps in README.md in the `scripts/setup` directory.
## Build and run

```bash
cd .docker/nuc
docker compose -f docker-compose-nuc.yaml build
docker compose -f docker-compose-nuc.yaml up
```

No environment variables are required; the compose file is self-contained.

# Laptop Setup

# Laptop Setup
In order to set up the user client on your laptop run `sudo ./laptop_setup.sh` from this [directory](https://github.com/droid-dataset/droid/tree/main/scripts/setup). Running through all the steps in this script will install host system dependencies and ensure the user client can be run in a docker container.

Further details can be found [README.md](https://github.com/droid-dataset/droid/tree/main/scripts/setup/README.md).
Further details can be found in the [README.md](https://github.com/droid-dataset/droid/tree/main/scripts/setup/README.md) in the `scripts/setup` directory.

# Docker resources

* [Docker Overview](https://docs.docker.com/get-started/overview/)
* [Dockerfile Reference](https://docs.docker.com/engine/reference/builder/)
* [Docker Compose Overview](https://docs.docker.com/compose/)
7 changes: 3 additions & 4 deletions .docker/laptop/Dockerfile.laptop
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM nvidia/cuda:12.1.0-devel-ubuntu22.04

# build args
# no ROBOT_IP/NUC_IP here: inside the container a build-time ENV would shadow
# the defaults in the bind-mounted parameters.py; the IPs come from the
# runtime environment (docker-compose / ansible) or from parameters.py itself
ARG ROBOT_TYPE=${ROBOT_TYPE}
ARG LIBFRANKA_VERSION=${LIBFRANKA_VERSION}
ARG ROBOT_IP=${ROBOT_IP}
ARG NUC_IP=${NUC_IP}
ARG NUC_ROBOT_CONFIG_DIR=/app/config/${ROBOT_TYPE}
ARG NUC_OCULUS_DIR=/app/droid/oculus_reader
ARG NUC_POLYMETIS_DIR=/app/droid/fairo/polymetis
Expand All @@ -13,8 +14,6 @@ ARG NUC_POLYMETIS_CONFIG_DIR=${NUC_POLYMETIS_DIR}/polymetis/conf
# runtime env vars
ENV ROBOT_TYPE=${ROBOT_TYPE}
ENV LIBFRANKA_VERSION=${LIBFRANKA_VERSION}
ENV ROBOT_IP=${ROBOT_IP}
ENV NUC_IP=${NUC_IP}
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES \
${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}compute,video,utility
Expand Down
5 changes: 3 additions & 2 deletions .docker/laptop/Dockerfile.laptop_fr3
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM nvidia/cuda:12.1.0-devel-ubuntu22.04

# set robot parameters
# no baked-in ROBOT_IP/NUC_IP: inside the container a build-time ENV would
# shadow the defaults in the bind-mounted parameters.py; the IPs come from the
# runtime environment (docker-compose / ansible) or from parameters.py itself
ENV ROBOT_TYPE=fr3
ENV LIBFRANKA_VERSION=0.10.0
ENV ROBOT_IP=172.16.0.1
ENV NUC_IP=172.16.0.2
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES \
${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}compute,video,utility
Expand Down
5 changes: 3 additions & 2 deletions .docker/laptop/Dockerfile.laptop_panda
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM nvidia/cuda:12.1.0-devel-ubuntu22.04

# set robot parameters
# no baked-in ROBOT_IP/NUC_IP: inside the container a build-time ENV would
# shadow the defaults in the bind-mounted parameters.py; the IPs come from the
# runtime environment (docker-compose / ansible) or from parameters.py itself
ENV ROBOT_TYPE=panda
ENV LIBFRANKA_VERSION=0.9.0
ENV ROBOT_IP=172.16.0.1
ENV NUC_IP=172.16.0.2
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES \
${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}compute,video,utility
Expand Down
8 changes: 5 additions & 3 deletions .docker/laptop/docker-compose-laptop-data-upload.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ services:
XAUTHORITY: ${DOCKER_XAUTH}
ROBOT_TYPE: ${ROBOT_TYPE}
LIBFRANKA_VERSION: ${LIBFRANKA_VERSION}
NUC_IP: ${NUC_IP}
ROBOT_IP: ${ROBOT_IP}
LAPTOP_IP: ${LAPTOP_IP}
# machine IPs: override the defaults in the bind-mounted parameters.py;
# empty (unset on the host) falls back to the file's values
NUC_IP: ${NUC_IP:-}
ROBOT_IP: ${ROBOT_IP:-}
LAPTOP_IP: ${LAPTOP_IP:-}
NVIDIA_VISIBLE_DEVICES: all
ANDROID_ADB_SERVER_ADDRESS: host.docker.internal
volumes:
Expand Down
10 changes: 5 additions & 5 deletions .docker/laptop/docker-compose-laptop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ services:
XAUTHORITY: ${DOCKER_XAUTH}
ROBOT_TYPE: ${ROBOT_TYPE}
LIBFRANKA_VERSION: ${LIBFRANKA_VERSION}
NUC_IP: ${NUC_IP}
ROBOT_IP: ${ROBOT_IP}
LAPTOP_IP: ${LAPTOP_IP}
# machine IPs: override the defaults in the bind-mounted parameters.py;
# empty (unset on the host) falls back to the file's values
NUC_IP: ${NUC_IP:-}
ROBOT_IP: ${ROBOT_IP:-}
LAPTOP_IP: ${LAPTOP_IP:-}
NVIDIA_VISIBLE_DEVICES: all
ANDROID_ADB_SERVER_ADDRESS: host.docker.internal
volumes:
Expand All @@ -27,8 +29,6 @@ services:
args:
ROOT_DIR: ${ROOT_DIR}
ROBOT_TYPE: ${ROBOT_TYPE}
NUC_IP: ${NUC_IP}
ROBOT_IP: ${ROBOT_IP}
devices:
- "/dev:/dev"
runtime: nvidia
Expand Down
122 changes: 56 additions & 66 deletions .docker/nuc/Dockerfile.nuc
Original file line number Diff line number Diff line change
@@ -1,81 +1,71 @@
# pull ubuntu base image
FROM ubuntu:bionic
# Control-server image for the Franka Panda NUC.
#
# The image is fully self-contained: code, compiled libfranka/polymetis, and
# the polymetis config (which lives in the droid/fairo submodule) are all baked
# in at build time. Everything machine-specific enters at runtime — see
# docker-compose-nuc.yaml and ../README.md for the runtime contract.
#
# Requires submodules to be checked out: git submodule update --init --recursive

# build args
ARG ROBOT_TYPE=${ROBOT_TYPE}
ARG LIBFRANKA_VERSION=${LIBFRANKA_VERSION}
ARG ROBOT_IP=${ROBOT_IP}
ARG NUC_IP=${NUC_IP}
ARG NUC_POLYMETIS_DIR=/app/droid/fairo/polymetis
ARG NUC_ROBOT_CONFIG_DIR=/app/config/${ROBOT_TYPE}
ARG NUC_POLYMETIS_CONFIG_DIR=${NUC_POLYMETIS_DIR}/polymetis/conf
# micromamba binary donor stage; fully qualified so podman/buildah resolve it too
FROM docker.io/mambaorg/micromamba:1.5.10 AS micromamba

# runtime env vars
ENV ROBOT_TYPE=${ROBOT_TYPE}
ENV LIBFRANKA_VERSION=${LIBFRANKA_VERSION}
ENV ROBOT_IP=${ROBOT_IP}
ENV NUC_IP=${NUC_IP}
FROM docker.io/library/ubuntu:noble

# base system installations
ARG POLYMETIS_DIR=/app/droid/fairo/polymetis
ARG LIBFRANKA_DIR=${POLYMETIS_DIR}/polymetis/src/clients/franka_panda_client/third_party/libfranka

# base system: the toolchain (gcc, make, cmake) and all libraries come from the
# conda env. ca-certificates is required for micromamba's TLS downloads; sudo is
# needed at runtime by polymetis' realtime server launch; procps (pgrep) and
# psmisc (fuser) by supervise_launch.sh's stale-process reaping.
RUN apt-get update && \
apt-get install -y software-properties-common build-essential sudo git curl wget python3-pip libspdlog-dev \
libeigen3-dev lsb-release ffmpeg libsm6 libxext6 && \
apt-get upgrade -y
apt-get install -y --no-install-recommends ca-certificates sudo procps psmisc nano && \
rm -rf /var/lib/apt/lists/*

# install latest cmake version
RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null && \
apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \
apt-get update && \
apt-get install -y cmake
# tini as PID 1, installed per https://github.com/krallin/tini#using-tini
ENV TINI_VERSION=v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini

# copy project code to container
COPY . /app
WORKDIR /app
# micromamba instead of miniconda: its libsolv solver resolves the polymetis
# environment in ~2 minutes where the classic conda solver takes hours
COPY --from=micromamba /bin/micromamba /usr/local/bin/micromamba
ENV MAMBA_ROOT_PREFIX=/opt/micromamba

# install miniconda
RUN wget \
https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \
&& mkdir /root/.conda \
&& bash Miniconda3-latest-Linux-x86_64.sh -b \
&& rm -f Miniconda3-latest-Linux-x86_64.sh
ENV PATH /root/miniconda3/bin:$PATH
COPY droid/fairo/polymetis/polymetis/environment.lock /tmp/environment.lock
RUN micromamba create -y -n polymetis-local -f /tmp/environment.lock && \
micromamba clean -a -y

# setup polymetis conda env
WORKDIR ${NUC_POLYMETIS_DIR}
RUN conda env create -f ./polymetis/environment.yml && \
conda run -n polymetis-local pip install -e ./polymetis
SHELL ["conda", "run", "-n", "polymetis-local", "/bin/bash", "-c"]
COPY droid/fairo /app/droid/fairo

# build libfranka and polymetis
RUN ./scripts/build_libfranka.sh ${LIBFRANKA_VERSION} && \
mkdir -p ./polymetis/build && \
cd ./polymetis/build && \
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_FRANKA=ON -DBUILD_TESTS=ON -DBUILD_DOCS=ON && \
make -j
# fail fast if submodules weren't checked out
RUN test -f ${LIBFRANKA_DIR}/CMakeLists.txt && test -n "$(ls -A ${LIBFRANKA_DIR}/common)" || \
{ echo "libfranka submodule missing or incomplete: run 'git submodule update --init --recursive' first"; exit 1; }

# set polymetis config files
WORKDIR /app
RUN cp ${NUC_ROBOT_CONFIG_DIR}/franka_hardware.yaml ${NUC_POLYMETIS_CONFIG_DIR}/robot_client/franka_hardware.yaml && \
cp ${NUC_ROBOT_CONFIG_DIR}/franka_panda.yaml ${NUC_POLYMETIS_CONFIG_DIR}/robot_model/franka_panda.yaml
WORKDIR ${POLYMETIS_DIR}
RUN micromamba run -n polymetis-local pip install -e ./polymetis

# python environment setup
RUN pip3 install -e . && \
pip3 install dm-robotics-moma==0.5.0 --no-deps && \
pip3 install dm-robotics-transformations==0.5.0 --no-deps && \
pip3 install dm-robotics-agentflow==0.5.0 --no-deps && \
pip3 install dm-robotics-geometry==0.5.0 --no-deps && \
pip3 install dm-robotics-manipulation==0.5.0 --no-deps && \
pip3 install dm-robotics-controllers==0.5.0 --no-deps
# build libfranka from the checked-out submodule, pinned by fairo's gitlink to
# 0.9.0 — the version compatible with the Panda (fr3 would need 0.10.0)
WORKDIR ${LIBFRANKA_DIR}/build
RUN micromamba run -n polymetis-local cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=ON ..
RUN micromamba run -n polymetis-local cmake --build . --parallel "$(nproc)"

# using miniconda instead of anaconda so overwrite sh scripts
RUN find /app/droid/franka -type f -name "launch_*.sh" -exec sed -i 's/anaconda/miniconda/g' {} \;
RUN find /app/scripts/server -type f -name "launch_server.sh" -exec sed -i 's/anaconda/miniconda/g' {} \;
# build polymetis
WORKDIR ${POLYMETIS_DIR}/polymetis/build
RUN micromamba run -n polymetis-local cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_FRANKA=ON -DBUILD_TESTS=OFF -DBUILD_DOCS=OFF
RUN micromamba run -n polymetis-local make -j"$(nproc)"

# set absolute paths
RUN find /app/droid/franka -type f -name "launch_*.sh" -exec sed -i 's|~|/root|g' {} \;
RUN find /app/scripts/server -type f -name "launch_server.sh" -exec sed -i 's|~|/root|g' {} \;
# copy the rest of the project and install the droid package into the same env
COPY . /app
WORKDIR /app
RUN micromamba run -n polymetis-local pip install --no-cache-dir -e .

# start the server
RUN chmod +x /app/scripts/server/launch_server.sh
ENTRYPOINT ["/app/scripts/server/launch_server.sh"]
# the launch scripts in droid/franka target the bare-metal pixi setup on the
# NUC; replace them with the conda-based equivalents for this image
COPY .docker/nuc/launch_robot.sh .docker/nuc/launch_gripper.sh .docker/nuc/supervise_launch.sh /app/droid/franka/

ENTRYPOINT ["/tini", "--", \
"/usr/local/bin/micromamba", "run", "-r", "/opt/micromamba", "-n", "polymetis-local", \
"python", "/app/scripts/server/run_server.py"]
77 changes: 0 additions & 77 deletions .docker/nuc/Dockerfile.nuc_fr3

This file was deleted.

Loading