# syntax=docker/dockerfile:1.7 # Final target: # Ubuntu 24.04 + NVIDIA GLX + XFCE + PipeWire + Firefox + Selkies # # The build uses intermediate stages, but produces a single final image. ARG DISTRIB_RELEASE=24.04 ARG SELKIES_IMAGE=ghcr.io/selkies-project/selkies/py-build:main ARG SELKIES_GIT_REF=main ARG NVIDIA_VAAPI_DRIVER_VERSION=latest # ----------------------------------------------------------------------------- # Current Selkies wheel # ----------------------------------------------------------------------------- FROM ${SELKIES_IMAGE} AS selkies-build # ----------------------------------------------------------------------------- # Selkies joystick interposer # ----------------------------------------------------------------------------- # Build the Selkies joystick interposer directly from its official source. FROM docker.io/library/ubuntu:${DISTRIB_RELEASE} AS selkies-js-interposer-builder ARG DEBIAN_FRONTEND=noninteractive ARG SELKIES_GIT_REF SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update \ && apt-get install --no-install-recommends -y \ build-essential \ ca-certificates \ curl \ && rm -rf /var/lib/apt/lists/* RUN set -eux; \ curl -fsSL \ "https://raw.githubusercontent.com/selkies-project/selkies/${SELKIES_GIT_REF}/addons/js-interposer/joystick_interposer.c" \ -o /tmp/joystick_interposer.c; \ install -d -m 0755 /out; \ gcc -shared -fPIC -O2 \ -Wl,-z,relro,-z,now \ -o /out/selkies_joystick_interposer.so \ /tmp/joystick_interposer.c \ -ldl; \ test -s /out/selkies_joystick_interposer.so # ----------------------------------------------------------------------------- # Build the current nvidia-vaapi-driver without retaining build dependencies # ----------------------------------------------------------------------------- FROM docker.io/library/ubuntu:${DISTRIB_RELEASE} AS nvidia-vaapi-builder ARG DEBIAN_FRONTEND=noninteractive ARG NVIDIA_VAAPI_DRIVER_VERSION SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update \ && apt-get install --no-install-recommends -y \ ca-certificates \ curl \ gcc \ jq \ meson \ ninja-build \ pkg-config \ libdrm-dev \ libegl-dev \ libffmpeg-nvenc-dev \ libgstreamer-plugins-bad1.0-dev \ libva-dev \ && rm -rf /var/lib/apt/lists/* RUN set -eux; \ version="${NVIDIA_VAAPI_DRIVER_VERSION}"; \ if [[ "${version}" == "latest" ]]; then \ version="$(curl -fsSL https://api.github.com/repos/elFarto/nvidia-vaapi-driver/releases/latest \ | jq -r '.tag_name' \ | sed 's/^v//')"; \ fi; \ curl -fsSL \ "https://github.com/elFarto/nvidia-vaapi-driver/archive/refs/tags/v${version}.tar.gz" \ -o /tmp/nvidia-vaapi-driver.tar.gz; \ mkdir -p /tmp/nvidia-vaapi-driver; \ tar -xzf /tmp/nvidia-vaapi-driver.tar.gz \ --strip-components=1 \ -C /tmp/nvidia-vaapi-driver; \ cd /tmp/nvidia-vaapi-driver; \ meson setup build \ --prefix=/usr \ --buildtype=release; \ meson compile -C build; \ DESTDIR=/out meson install -C build # ----------------------------------------------------------------------------- # Final image # ----------------------------------------------------------------------------- FROM docker.io/library/ubuntu:${DISTRIB_RELEASE} ARG DEBIAN_FRONTEND=noninteractive ARG DISTRIB_RELEASE ARG TZ=UTC ARG USER_NAME=ubuntu ARG USER_UID=1000 ARG USER_GID=1000 LABEL org.opencontainers.image.title="Selkies NVIDIA XFCE Desktop" \ org.opencontainers.image.description="XFCE remote desktop with Selkies, PipeWire and NVIDIA acceleration" \ org.opencontainers.image.source="https://github.com/selkies-project/selkies" SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV TZ="${TZ}" \ LANG="en_US.UTF-8" \ LANGUAGE="en_US:en" \ LC_ALL="en_US.UTF-8" \ PASSWD="mypasswd" \ DISPLAY=":20" \ DISPLAY_SIZEW="1920" \ DISPLAY_SIZEH="1080" \ DISPLAY_REFRESH="60" \ DISPLAY_DPI="96" \ DISPLAY_CDEPTH="24" \ VIDEO_PORT="DFP" \ DESKTOP_SESSION="xfce" \ XDG_SESSION_DESKTOP="xfce" \ XDG_CURRENT_DESKTOP="XFCE" \ XDG_SESSION_TYPE="x11" \ SELKIES_MODE="websockets" \ SELKIES_PORT="8080" \ SELKIES_ENCODER="h264enc" \ SELKIES_ENABLE_RESIZE="false" \ SELKIES_ENABLE_BASIC_AUTH="true" \ NVIDIA_VISIBLE_DEVICES="all" \ NVIDIA_DRIVER_CAPABILITIES="all" \ __GL_SYNC_TO_VBLANK="0" \ __GLX_VENDOR_LIBRARY_NAME="nvidia" \ LIBVA_DRIVER_NAME="nvidia" \ NVD_BACKEND="direct" \ MOZ_DISABLE_RDD_SANDBOX="1" \ MOZ_X11_EGL="1" \ PIPEWIRE_LATENCY="128/48000" \ XDG_RUNTIME_DIR="/tmp/runtime-ubuntu" \ PIPEWIRE_RUNTIME_DIR="/tmp/runtime-ubuntu" \ PULSE_RUNTIME_PATH="/tmp/runtime-ubuntu/pulse" \ PULSE_SERVER="unix:/tmp/runtime-ubuntu/pulse/native" \ DBUS_SYSTEM_BUS_ADDRESS="unix:path=/tmp/runtime-ubuntu/dbus-system-bus" \ APPIMAGE_EXTRACT_AND_RUN="1" \ SUDO_EDITOR="mousepad" # Bootstrap packages needed to configure APT repositories. RUN apt-get update \ && apt-get install --no-install-recommends -y \ ca-certificates \ curl \ gnupg \ locales \ ssl-cert \ tzdata \ && locale-gen en_US.UTF-8 \ && ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime \ && echo "${TZ}" > /etc/timezone \ && rm -rf /var/lib/apt/lists/* # Keep the same Mozilla and PipeWire repositories as the original image. RUN install -d -m 0755 \ /etc/apt/preferences.d \ /etc/apt/sources.list.d \ /etc/apt/trusted.gpg.d \ && printf '%s\n' \ 'Package: firefox*' \ 'Pin: version 1:1snap*' \ 'Pin-Priority: -1' \ > /etc/apt/preferences.d/firefox-nosnap \ && curl -fsSL \ 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x738BEB9321D1AAEC13EA9391AEBDF4819BE21867' \ | gpg --dearmor \ > /etc/apt/trusted.gpg.d/mozillateam-ubuntu-ppa.gpg \ && echo \ "deb https://ppa.launchpadcontent.net/mozillateam/ppa/ubuntu noble main" \ > /etc/apt/sources.list.d/mozillateam-ubuntu-ppa.list \ && curl -fsSL \ 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFC43B7352BCC0EC8AF2EEB8B25088A0359807596' \ | gpg --dearmor \ > /etc/apt/trusted.gpg.d/pipewire-debian-ubuntu.gpg \ && echo \ "deb https://ppa.launchpadcontent.net/pipewire-debian/pipewire-upstream/ubuntu noble main" \ > /etc/apt/sources.list.d/pipewire-upstream.list \ && echo \ "deb https://ppa.launchpadcontent.net/pipewire-debian/wireplumber-upstream/ubuntu noble main" \ > /etc/apt/sources.list.d/wireplumber-upstream.list # Base system, desktop, Xorg, NVIDIA runtime interfaces and user tools. # There are deliberately no Intel/AMD VA-API or Vulkan drivers and no i386 # architecture. RUN apt-get update \ && apt-get install --no-install-recommends -y \ apt-utils \ bash-completion \ binutils \ btop \ bzip2 \ clinfo \ dbus-user-session \ dbus-x11 \ desktop-file-utils \ dnsutils \ file \ firefox \ fonts-dejavu \ fonts-liberation \ fonts-noto \ fonts-noto-cjk \ fonts-noto-color-emoji \ fonts-noto-mono \ fonts-ubuntu \ fuse \ git \ gvfs \ jq \ kmod \ less \ libdrm2 \ libegl1 \ libelf-dev \ libgcrypt20 \ libgl1 \ libgles1 \ libgles2 \ libglu1-mesa \ libglvnd-dev \ libglvnd0 \ libglx0 \ libgstreamer-plugins-bad1.0-0 \ libopengl0 \ libopus0 \ libpci3 \ libpulse0 \ libsm6 \ libva-drm2 \ libva-x11-2 \ libva2 \ libvulkan1 \ libx11-6 \ libx11-xcb1 \ libxau6 \ libxcb-dri3-0 \ libxcb1 \ libxdamage1 \ libxdmcp6 \ libxext6 \ libxfixes3 \ libxkbcommon0 \ libxtst6 \ libxv1 \ mousepad \ nano \ neofetch \ net-tools \ ocl-icd-libopencl1 \ pavucontrol \ pciutils \ procps \ psmisc \ python3 \ python3-pip \ python3-venv \ ristretto \ sudo \ supervisor \ thunar \ tumbler \ udev \ unzip \ vainfo \ vim \ vulkan-tools \ wget \ wmctrl \ x11-apps \ x11-utils \ x11-xkb-utils \ x11-xserver-utils \ x264 \ x265 \ xauth \ xbitmaps \ xclip \ xcvt \ xdg-user-dirs \ xdg-utils \ xfce4 \ xfce4-goodies \ xfce4-notifyd \ xfce4-pulseaudio-plugin \ xfce4-terminal \ xfonts-base \ xfonts-scalable \ xinit \ xkb-data \ xsel \ xserver-xorg-core \ xserver-xorg-input-libinput \ xserver-xorg-legacy \ xsettingsd \ xterm \ xdotool \ xz-utils \ zip \ zstd \ pipewire \ pipewire-alsa \ pipewire-audio-client-libraries \ pipewire-jack \ pipewire-libcamera \ pipewire-locales \ pipewire-v4l2 \ pipewire-vulkan \ gstreamer1.0-libcamera \ gstreamer1.0-pipewire \ gir1.2-wp-0.5 \ libpipewire-0.3-modules \ libpipewire-module-x11-bell \ libspa-0.2-bluetooth \ libspa-0.2-jack \ libspa-0.2-modules \ wireplumber \ wireplumber-locales \ && apt-get clean \ && rm -rf \ /var/lib/apt/lists/* \ /var/cache/apt/* \ /var/cache/debconf/* \ /var/log/* \ /tmp/* \ /var/tmp/* # Install only the compiled NVIDIA VA-API runtime from the builder stage. COPY --from=nvidia-vaapi-builder /out/usr/ /usr/ # Make NVIDIA libraries injected by NVIDIA Container Toolkit discoverable. RUN printf '%s\n' \ '/usr/local/nvidia/lib' \ '/usr/local/nvidia/lib64' \ > /etc/ld.so.conf.d/nvidia.conf \ && install -d -m 0755 \ /etc/OpenCL/vendors \ /etc/vulkan/icd.d \ /usr/share/glvnd/egl_vendor.d \ && echo 'libnvidia-opencl.so.1' \ > /etc/OpenCL/vendors/nvidia.icd \ && cat > /etc/vulkan/icd.d/nvidia_icd.json <<'EOF' { "file_format_version": "1.0.0", "ICD": { "library_path": "libGLX_nvidia.so.0", "api_version": "1.3.0" } } EOF RUN cat > /usr/share/glvnd/egl_vendor.d/10_nvidia.json <<'EOF' { "file_format_version": "1.0.0", "ICD": { "library_path": "libEGL_nvidia.so.0" } } EOF ENV PATH="/opt/selkies/bin:/usr/local/nvidia/bin:${PATH}" \ LD_LIBRARY_PATH="/usr/local/nvidia/lib:/usr/local/nvidia/lib64" # Install the Selkies wheel from the official stable build image. COPY --from=selkies-build /opt/pypi/dist/selkies-*.whl /tmp/ RUN python3 -m venv /opt/selkies \ && /opt/selkies/bin/python -m pip install \ --no-cache-dir \ --upgrade \ pip \ && /opt/selkies/bin/python -m pip install \ --no-cache-dir \ /tmp/selkies-*.whl \ && rm -f /tmp/selkies-*.whl COPY --from=selkies-js-interposer-builder \ /out/selkies_joystick_interposer.so \ /usr/local/lib/selkies_joystick_interposer.so RUN chmod 0755 /usr/local/lib/selkies_joystick_interposer.so # Create the regular desktop user. sudo-root is kept because Xorg and the # NVIDIA userspace installer need a few targeted root operations at runtime. RUN groupadd --gid "${USER_GID}" "${USER_NAME}" \ && useradd \ --uid "${USER_UID}" \ --gid "${USER_GID}" \ --create-home \ --shell /bin/bash \ "${USER_NAME}" \ && for group in \ adm audio cdrom dialout dip fax floppy games input lp plugdev render \ ssl-cert sudo tape tty video voice; \ do \ getent group "${group}" >/dev/null \ && usermod -aG "${group}" "${USER_NAME}" \ || true; \ done \ && echo "${USER_NAME} ALL=(ALL:ALL) NOPASSWD: ALL" \ > "/etc/sudoers.d/${USER_NAME}" \ && chmod 0440 "/etc/sudoers.d/${USER_NAME}" \ && echo "${USER_NAME}:${PASSWD}" | chpasswd \ && cp -a /usr/bin/sudo /usr/bin/sudo-root \ && chown root:root /usr/bin/sudo-root \ && chmod 4755 /usr/bin/sudo-root \ && install -d \ -o "${USER_UID}" \ -g "${USER_GID}" \ -m 0700 \ /tmp/runtime-ubuntu \ && chown -R \ "${USER_UID}:${USER_GID}" \ "/home/${USER_NAME}" \ /etc/X11 \ /opt/selkies # XFCE defaults suitable for a permanently streamed desktop. RUN install -d -m 0755 \ /etc/xdg/xfce4/xfconf/xfce-perchannel-xml \ /etc/firefox/policies \ && if [[ -f /etc/xdg/xfce4/panel/default.xml ]]; then \ cp -f \ /etc/xdg/xfce4/panel/default.xml \ /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml; \ fi \ && cat > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml <<'EOF' EOF RUN cat > /etc/firefox/policies/policies.json <<'EOF' { "policies": { "Preferences": { "gfx.x11-egl.force-enabled": { "Value": true, "Status": "default" }, "media.ffmpeg.vaapi.enabled": { "Value": true, "Status": "default" }, "media.hardware-video-decoding.force-enabled": { "Value": true, "Status": "default" }, "media.rdd-ffmpeg.enabled": { "Value": true, "Status": "default" } } } } EOF RUN update-alternatives --set x-www-browser /usr/bin/firefox \ || true # The three files below must be placed beside this Dockerfile. COPY --chown=${USER_UID}:${USER_GID} entrypoint.sh /etc/entrypoint.sh COPY --chown=${USER_UID}:${USER_GID} selkies-entrypoint.sh /etc/selkies-entrypoint.sh COPY --chown=${USER_UID}:${USER_GID} supervisord.conf /etc/supervisord.conf RUN chmod 0755 \ /etc/entrypoint.sh \ /etc/selkies-entrypoint.sh \ /etc/supervisord.conf USER ${USER_UID}:${USER_GID} ENV USER="${USER_NAME}" \ HOME="/home/${USER_NAME}" \ SHELL="/bin/bash" WORKDIR /home/${USER_NAME} EXPOSE 8080 ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]