# syntax=docker/dockerfile:1.7 # Ubuntu 24.04 + NVIDIA GLX + XFCE + PipeWire + Firefox. # # Important: the Selkies GStreamer runtime, Python wheel, web client and # joystick interposer all come from the SAME published Selkies release. # Do not mix these assets with py-build:main or a frontend built from main. ARG DISTRIB_RELEASE=24.04 ARG SELKIES_VERSION=1.6.2 ARG NVIDIA_VAAPI_DRIVER_VERSION=latest # ----------------------------------------------------------------------------- # Build nvidia-vaapi-driver without retaining its 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; \ install -d -m 0755 /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 SELKIES_VERSION ARG TZ=Europe/Paris 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 using the stable Selkies GStreamer release stack" \ org.opencontainers.image.source="https://github.com/selkies-project/docker-selkies-glx-desktop" 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" \ NGINX_PORT="8080" \ SELKIES_PORT="8081" \ SELKIES_ENCODER="nvh264enc" \ SELKIES_ENABLE_HTTPS="false" \ 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" \ PIP_BREAK_SYSTEM_PACKAGES="1" # Bootstrap APT and locales. 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 fr_FR.UTF-8 \ && ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime \ && echo "${TZ}" > /etc/timezone \ && rm -rf /var/lib/apt/lists/* # Firefox DEB and the same PipeWire/WirePlumber PPAs 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 # Desktop, Xorg, NVIDIA interfaces, PipeWire and the dependencies used by the # original Selkies GStreamer release. No KDE, Chrome, Wine, KasmVNC, RustDesk, # i386 stack, Intel VA-API stack or AMD/Mesa Vulkan stack. RUN apt-get update \ && apt-get install --no-install-recommends -y \ apache2-utils \ apt-utils \ alsa-utils \ aom-tools \ bash-completion \ binutils \ btop \ bzip2 \ clinfo \ coturn \ 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 \ glib-networking \ gstreamer1.0-libcamera \ gstreamer1.0-pipewire \ gstreamer1.0-plugins-bad \ gvfs \ jackd2 \ jq \ kmod \ less \ libdrm2 \ libegl1 \ libelf-dev \ libgcrypt20 \ libgirepository-1.0-1 \ libgl1 \ libgles1 \ libgles2 \ libglib2.0-0 \ libglvnd-dev \ libglvnd0 \ libglx0 \ libgstreamer-plugins-bad1.0-0 \ libgudev-1.0-0 \ libjack-jackd2-0 \ libopenh264-dev \ libopengl0 \ libopus0 \ libpci3 \ libpipewire-0.3-modules \ libpipewire-module-x11-bell \ libpulse0 \ libsm6 \ libspa-0.2-bluetooth \ libspa-0.2-jack \ libspa-0.2-modules \ libva-drm2 \ libva-x11-2 \ libva2 \ libvpx-dev \ libvulkan1 \ libwayland-dev \ libwayland-egl1 \ libx11-6 \ libx11-xcb1 \ libxau6 \ libxcb-dri3-0 \ libxcb1 \ libxdamage1 \ libxdmcp6 \ libxext6 \ libxfixes3 \ libxkbcommon0 \ libxtst6 \ libxv1 \ mousepad \ nano \ neofetch \ net-tools \ netcat-openbsd \ nginx \ ocl-icd-libopencl1 \ pavucontrol \ pciutils \ pipewire \ pipewire-alsa \ pipewire-audio-client-libraries \ pipewire-jack \ pipewire-libcamera \ pipewire-locales \ pipewire-v4l2 \ pipewire-vulkan \ procps \ psmisc \ python3 \ python3-dev \ python3-gi \ python3-pip \ python3-setuptools \ python3-venv \ python3-wheel \ ristretto \ sudo \ supervisor \ svt-av1 \ thunar \ tumbler \ udev \ unzip \ vainfo \ vim \ vulkan-tools \ wayland-protocols \ wget \ wireplumber \ wireplumber-locales \ 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 \ gir1.2-wp-0.5 \ && sed -i \ -e 's#/var/log/nginx/access\.log#/dev/stdout#g' \ -e 's#/var/log/nginx/error\.log#/dev/stderr#g' \ -e 's#/run/nginx\.pid#/tmp/nginx.pid#g' \ /etc/nginx/nginx.conf \ && printf '\nerror_log /dev/stderr;\n' >> /etc/nginx/nginx.conf \ && apt-get clean \ && rm -rf \ /var/lib/apt/lists/* \ /var/cache/apt/* \ /var/cache/debconf/* \ /var/log/* \ /tmp/* \ /var/tmp/* # Keep only the compiled NVIDIA VA-API runtime. COPY --from=nvidia-vaapi-builder /out/usr/ /usr/ # NVIDIA libraries are injected by the NVIDIA GPU Operator / Container Toolkit. 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 <<'JSON' { "file_format_version": "1.0.0", "ICD": { "library_path": "libGLX_nvidia.so.0", "api_version": "1.3.0" } } JSON RUN cat > /usr/share/glvnd/egl_vendor.d/10_nvidia.json <<'JSON' { "file_format_version": "1.0.0", "ICD": { "library_path": "libEGL_nvidia.so.0" } } JSON ENV PATH="/usr/local/nvidia/bin:${PATH}" \ LD_LIBRARY_PATH="/usr/local/nvidia/lib:/usr/local/nvidia/lib64" # Install every Selkies component from the same stable v1.6.2 release. RUN set -eux; \ ubuntu_version="$(. /etc/os-release; printf '%s' "${VERSION_ID}")"; \ arch="$(dpkg --print-architecture)"; \ release_url="https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}"; \ curl -fsSL \ "${release_url}/gstreamer-selkies_gpl_v${SELKIES_VERSION}_ubuntu${ubuntu_version}_${arch}.tar.gz" \ | tar -xzf - -C /opt; \ wheel_path="/tmp/selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl"; \ curl -fsSL \ "${release_url}/selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" \ -o "${wheel_path}"; \ python3 -m pip install \ --break-system-packages \ --no-cache-dir \ --force-reinstall \ "${wheel_path}" \ 'websockets<14.0'; \ curl -fsSL \ "${release_url}/selkies-gstreamer-web_v${SELKIES_VERSION}.tar.gz" \ | tar -xzf - -C /opt; \ curl -fsSL \ "${release_url}/selkies-js-interposer_v${SELKIES_VERSION}_ubuntu${ubuntu_version}_${arch}.deb" \ -o /tmp/selkies-js-interposer.deb; \ apt-get update; \ apt-get install --no-install-recommends -y \ /tmp/selkies-js-interposer.deb; \ test -f /opt/gst-web/index.html; \ test -f /opt/gstreamer/gst-env; \ command -v selkies-gstreamer; \ rm -f \ "${wheel_path}" \ /tmp/selkies-js-interposer.deb; \ apt-get clean; \ rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* /var/tmp/* # Regular desktop user and the permissions required by the original entrypoints. RUN set -eux; \ if ! getent group "${USER_NAME}" >/dev/null; then \ groupadd --gid "${USER_GID}" "${USER_NAME}"; \ fi; \ if ! id -u "${USER_NAME}" >/dev/null 2>&1; then \ useradd \ --uid "${USER_UID}" \ --gid "${USER_NAME}" \ --create-home \ --shell /bin/bash \ "${USER_NAME}"; \ else \ usermod --shell /bin/bash "${USER_NAME}"; \ fi; \ 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 \ /etc/nginx \ /opt/gstreamer \ /opt/gst-web \ /var/lib/nginx # XFCE defaults for a permanently streamed desktop. RUN install -d -m 0755 \ /etc/xdg/xfce4/xfconf/xfce-perchannel-xml \ /etc/firefox/policies \ && cat > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml <<'XML' XML RUN cat > /etc/firefox/policies/policies.json <<'JSON' { "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" } } } } JSON RUN update-alternatives --set x-www-browser /usr/bin/firefox || true # Keep the ORIGINAL entrypoint and Selkies GStreamer entrypoint. # Replace supervisord.conf with the cleaned version supplied with this file. COPY --chown=${USER_UID}:${USER_GID} entrypoint.sh /etc/entrypoint.sh COPY --chown=${USER_UID}:${USER_GID} selkies-gstreamer-entrypoint.sh /etc/selkies-gstreamer-entrypoint.sh COPY --chown=${USER_UID}:${USER_GID} supervisord.conf /etc/supervisord.conf RUN sed -i 's/\r$//' \ /etc/entrypoint.sh \ /etc/selkies-gstreamer-entrypoint.sh \ /etc/supervisord.conf \ && chmod 0755 \ /etc/entrypoint.sh \ /etc/selkies-gstreamer-entrypoint.sh \ /etc/supervisord.conf # Original coTURN helper used by selkies-gstreamer-entrypoint.sh. RUN cat > /etc/start-turnserver.sh <<'EOF_TURN' #!/bin/bash set -e exec turnserver \ --verbose \ --listening-ip="0.0.0.0" \ --listening-ip="::" \ --listening-port="${SELKIES_TURN_PORT:-3478}" \ --realm="${TURN_REALM:-example.com}" \ --external-ip="${TURN_EXTERNAL_IP:-127.0.0.1}" \ --min-port="${TURN_MIN_PORT:-49152}" \ --max-port="${TURN_MAX_PORT:-65535}" \ --channel-lifetime="${TURN_CHANNEL_LIFETIME:--1}" \ --lt-cred-mech \ --user="selkies:${TURN_RANDOM_PASSWORD}" \ --no-cli \ --cli-password="${TURN_RANDOM_PASSWORD}" \ --userdb="${XDG_RUNTIME_DIR:-/tmp}/turnserver-turndb" \ --pidfile="${XDG_RUNTIME_DIR:-/tmp}/turnserver.pid" \ --log-file="stdout" \ --allow-loopback-peers \ ${TURN_EXTRA_ARGS} "$@" EOF_TURN RUN chmod 0755 /etc/start-turnserver.sh 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"]