FROM oven/bun:1.3.10 AS frontend WORKDIR /build COPY frontend/package.json frontend/bun.lock ./ RUN bun install --frozen-lockfile COPY frontend/ ./ RUN bun run build # Build Flora's dependencies on CPU without credentials or Flora model weights. # Verified upstream PyTorch image digest, CUDA 12.8 / Torch 2.11.0. FROM pytorch/pytorch:2.11.0-cuda12.8-cudnn9-devel@sha256:53ab3de62f6101d1e42f9be28623ab7a468a24c070d632f211ed576e30b6abd3 AS flora-build ENV DEBIAN_FRONTEND=noninteractive TORCH_CUDA_ARCH_LIST=9.0 MAX_JOBS=4 RUN apt-get update && apt-get install -y --no-install-recommends python3-venv \ && rm -rf /var/lib/apt/lists/* \ && python -m venv --system-site-packages /opt/flora-venv ENV PATH=/opt/flora-venv/bin:$PATH WORKDIR /workspace/app COPY worker-requirements.txt flora-requirements.txt flora-acceleration-requirements.txt ./ # Keep the reference worker/native dependency overlay order. RUN python -m pip install --no-cache-dir -r worker-requirements.txt RUN python -m pip install --no-cache-dir -r flora-requirements.txt COPY build_causal_conv1d.py ./ ENV MAX_JOBS=1 RUN python build_causal_conv1d.py RUN python -m pip install --no-cache-dir --no-build-isolation -r flora-acceleration-requirements.txt RUN python -c "import causal_conv1d; from fla.ops.gated_delta_rule import chunk_gated_delta_rule, fused_recurrent_gated_delta_rule" COPY *.py *.txt prompt-templates.json pipeline-source.tar.gz pipeline-version.json ./ RUN python bootstrap.py COPY requirements.txt ./ RUN python -m venv --system-site-packages /opt/studio-venv RUN /opt/studio-venv/bin/python -m pip install --no-cache-dir -r requirements.txt RUN /opt/flora-venv/bin/python -c "from importlib.metadata import version; assert version('huggingface_hub') == '1.33.0'; assert version('transformers') == '5.8.1'" # Camera operators arrive as verified wheels; this image needs no CUDA compiler. FROM pytorch/pytorch:2.11.0-cuda12.8-cudnn9-runtime@sha256:eee11b3b3872a8c838e35ef48f08b2d5def2080902c7f666831310ca1a0ef2be AS hand-base ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends git ffmpeg libglib2.0-0 python3-venv \ && rm -rf /var/lib/apt/lists/* # Isolate the controller, camera and hand dependencies to retain their CUDA ABIs. FROM hand-base AS hand-build ENV UV_PYTHON_INSTALL_DIR=/opt/uv-python RUN python -m pip install --break-system-packages --no-cache-dir uv==0.12.11 \ && uv python install 3.10.20 \ && uv venv --python 3.10.20 /opt/droid \ && uv venv --python 3.10.20 /opt/egohand \ && python -m venv --system-site-packages /opt/hand-controller WORKDIR /opt/hand-worker COPY hand-runtime/camera-requirements.txt hand-runtime/egohand-requirements.txt hand-runtime/worker-requirements.txt ./ RUN uv pip install --python /opt/droid/bin/python torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128 RUN uv pip install --python /opt/droid/bin/python -r /opt/hand-worker/camera-requirements.txt \ && uv pip install --python /opt/droid/bin/python torch-scatter==2.1.2+pt28cu128 --find-links https://data.pyg.org/whl/torch-2.8.0+cu128.html \ && uv pip install --python /opt/droid/bin/python git+https://github.com/microsoft/MoGe.git@74fbce054ebed49800de42d0ad0e83495065719a --no-deps RUN uv pip install --python /opt/egohand/bin/python -r /opt/hand-worker/egohand-requirements.txt \ && /opt/hand-controller/bin/python -m pip install --no-cache-dir -r /opt/hand-worker/worker-requirements.txt RUN git clone https://github.com/NVlabs/GEM-X.git /opt/GEM-X \ && git -C /opt/GEM-X checkout --detach 20555befe2fbbe5b2015e1fff7ff214dc18ea9d6 \ && uv pip install --python /opt/egohand/bin/python --no-deps -e /opt/GEM-X COPY hand-runtime/camera-adapter-source.tar.gz hand-runtime/camera-native-source.tar.gz /tmp/ RUN mkdir -p /opt/camera-adapter /opt/hand-camera \ && tar -xzf /tmp/camera-adapter-source.tar.gz -C /opt/camera-adapter \ && tar -xzf /tmp/camera-native-source.tar.gz -C /opt/hand-camera COPY hand-runtime/camera-wheels /opt/hand-worker/camera-wheels COPY hand-runtime/verify_camera_wheels.py hand-runtime/image-version.json /opt/hand-worker/ RUN python /opt/hand-worker/verify_camera_wheels.py /opt/hand-worker/camera-wheels /opt/hand-worker/image-version.json \ && uv pip install --python /opt/droid/bin/python --no-deps /opt/hand-worker/camera-wheels/wheels/*.whl # The frozen camera wrapper expects these source, checkpoint and plan aliases. RUN for source in /opt/hand-camera/release-195fps-verified/DROID-W/*; do \ name=$(basename "$source"); \ if [ ! -e "/opt/hand-camera/DROID-on/$name" ]; then ln -s "$source" "/opt/hand-camera/DROID-on/$name"; fi; \ done \ && mkdir -p /opt/hand-camera/moge-joint-vio/weights \ && ln -s /workspace/hand-assets/camera/moge-small.pt /opt/hand-camera/moge-joint-vio/weights/small.pt \ && ln -s /workspace/hand-assets/camera /opt/hand-camera/release-195fps-verified/DROID-W/pretrained \ && ln -s /opt/hand-camera/release-195fps-verified/DROID-W/pretrained /opt/hand-camera/DROID-on/pretrained \ && ln -s /workspace/hand-cache/camera-artifacts /opt/hand-camera/speed-round24/artifacts RUN rm -rf /opt/hand-camera/release-195fps-verified/DROID-W/build \ /opt/hand-camera/release-195fps-verified/DROID-W/thirdparty/lietorch/build /tmp/*.tar.gz FROM hand-build AS hand-models COPY hand-runtime/model-assets/ /model-assets/ COPY hand-runtime/model-sources.json hand-runtime/build_model_assets.py hand-runtime/egohand-checkpoint-template.zip /opt/hand-worker/ RUN /opt/egohand/bin/python /opt/hand-worker/build_model_assets.py /model-assets /opt/hand-worker/model-sources.json FROM hand-base AS hand-runtime COPY --from=hand-build /opt/uv-python /opt/uv-python COPY --from=hand-build /opt/droid /opt/droid COPY --from=hand-build /opt/egohand /opt/egohand COPY --from=hand-build /opt/hand-controller /opt/hand-controller COPY --from=hand-build /opt/GEM-X /opt/GEM-X COPY --from=hand-build /opt/camera-adapter /opt/camera-adapter COPY --from=hand-build /opt/hand-camera /opt/hand-camera COPY hand-runtime/image-version.json hand-runtime/health.py hand-runtime/verify_runtime_imports.py /opt/hand-worker/ COPY hand-runtime/camera-startup-source.tar.gz /tmp/ COPY hand-runtime/camera-startup-sources.json hand-runtime/install_startup_sources.py /opt/hand-worker/ RUN python /opt/hand-worker/install_startup_sources.py /tmp/camera-startup-source.tar.gz /opt/hand-worker/camera-startup-sources.json /opt/hand-worker/image-version.json /opt/hand-camera \ && rm /tmp/camera-startup-source.tar.gz ENV PATH=/opt/hand-controller/bin:$PATH PYTHONUNBUFFERED=1 HF_HUB_DISABLE_TELEMETRY=1 PYTHONDONTWRITEBYTECODE=1 ENV HOME=/home/user XDG_CACHE_HOME=/home/user/.cache ENV PYTHONPATH=/opt/camera-adapter/src GDP_TRT_UPDATE=1 ENV DROID_SITE=/opt/droid/lib/python3.10/site-packages EGO_SITE=/opt/egohand/lib/python3.10/site-packages ENV DROID_LIBRARY_PATH=${DROID_SITE}/tensorrt_libs:${DROID_SITE}/nvidia/cu12/lib:${DROID_SITE}/nvidia/cudnn/lib:${DROID_SITE}/nvidia/cuda_runtime/lib:${DROID_SITE}/nvidia/cublas/lib:${DROID_SITE}/nvidia/cufft/lib:${DROID_SITE}/nvidia/curand/lib:${DROID_SITE}/nvidia/cuda_nvrtc/lib ENV EGO_LIBRARY_PATH=${EGO_SITE}/tensorrt_libs:${EGO_SITE}/nvidia/cudnn/lib:${EGO_SITE}/nvidia/cu13/lib:${DROID_SITE}/nvidia/cuda_runtime/lib:${DROID_SITE}/nvidia/cublas/lib:${DROID_SITE}/nvidia/cufft/lib:${DROID_SITE}/nvidia/curand/lib:${DROID_SITE}/nvidia/cuda_nvrtc/lib RUN if ! getent passwd 1000 >/dev/null; then useradd -m -u 1000 user; fi \ && mkdir -p /workspace /home/user/.cache && chown -R 1000:1000 /workspace /home/user USER 1000 WORKDIR /workspace COPY --from=hand-models --chown=1000:1000 /model-assets /workspace/hand-assets COPY hand-runtime/model-sources.json hand-runtime/build_model_assets.py /opt/hand-worker/ RUN python /opt/hand-worker/build_model_assets.py /workspace/hand-assets /opt/hand-worker/model-sources.json --verify-only RUN python /opt/hand-worker/verify_runtime_imports.py all # One public image supplies the Studio UI, Flora Jobs and native hand Jobs. FROM hand-runtime AS studio COPY --from=flora-build /opt/flora-venv /opt/flora-venv COPY --from=flora-build /opt/studio-venv /opt/studio-venv COPY --from=flora-build --chown=1000:1000 /workspace/source /workspace/source WORKDIR /workspace/app ENV PATH=/opt/flora-venv/bin:$PATH ENV PYTHONPATH=/workspace/source/src:/workspace/app PYTHONUNBUFFERED=1 HF_HUB_DISABLE_TELEMETRY=1 ENV FLORA_EXECUTION=accelerated FLORA_BATCH_SIZE=8 FLORA_ENABLE_INFERENCE=1 COPY --chown=1000:1000 *.py *.txt *.json pipeline-source.tar.gz Dockerfile README.md THIRD_PARTY.md VISUALIZER-LICENSE TRANSFORMERS-LICENSE /workspace/app/ COPY --from=frontend --chown=1000:1000 /build/dist /workspace/app/frontend/dist RUN python -c "import worker, torch, causal_conv1d; from fla.ops.gated_delta_rule import chunk_gated_delta_rule, fused_recurrent_gated_delta_rule; assert torch.__version__.split('+')[0] == '2.11.0'" RUN PYTHONPATH=/opt/camera-adapter/src /opt/hand-controller/bin/python /opt/hand-worker/verify_runtime_imports.py all USER 1000 EXPOSE 7860 CMD ["/opt/studio-venv/bin/python", "-m", "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860", "--no-access-log"]