Skip to content

GUI Desktop

With docker-nvidia-glx-desktop or docker-nvidia-egl-desktop, users may start a GUI Desktop interface accelerated with NVIDIA GPUs. Both containers support OpenGL and Vulkan. Out of the two containers, docker-nvidia-glx-desktop is generally recommended to be used because it provides the best performance. However, docker-nvidia-egl-desktop is versatile in various environments and has less processes running, meaning less possible errors. It is also possible to be used in HPC clusters with Apptainer/Singularity available, and sharing a GPU with multiple containers is also possible.

Support is available in #ue4research:matrix.nrp-nautilus.io (use this link if you use a matrix.org account), just ask your questions there.

Please give the repositories a star!

DNS Setup

Use the command ping turn.nrp-nautilus.io on your client (install iputils-ping when using Linux if the ping command does not work) to check that your DNS is correctly configured. If the latency is considerably high (over 60-70 miliseconds), consider changing your client DNS server to 8.8.8.8 and 8.8.4.4 or the DNS over HTTPS / DNS over TLS options provided by Google. CloudFlare's DNS server (addresses 1.1.1.1 and 1.0.0.1) may show issues locating the nearest relay server, which is crucial for performance.

Usage

The below is a reference configuration xgl.yml for docker-nvidia-glx-desktop.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: xgl
spec:
  replicas: 1
  selector:
    matchLabels:
      app: xgl
  template:
    metadata:
      labels:
        app: xgl
    spec:
      hostname: xgl
      containers:
      - name: xgl
        image: ghcr.io/selkies-project/nvidia-glx-desktop:latest
        env:
        - name: TZ
          value: "UTC"
        - name: SIZEW
          value: "1920"
        - name: SIZEH
          value: "1080"
        - name: REFRESH
          value: "60"
        - name: DPI
          value: "96"
        - name: CDEPTH
          value: "24"
        - name: VIDEO_PORT
          value: "DP-0"
        # Choose either `value:` or `secretKeyRef:` but not both at the same time
        - name: PASSWD
#          value: "mypasswd"
          valueFrom:
            secretKeyRef:
              name: my-pass
              key: my-pass
        # Uncomment this to enable noVNC, disabing selkies-gstreamer and ignoring all its parameters except `BASIC_AUTH_PASSWORD`, which will be used for authentication with noVNC, `BASIC_AUTH_PASSWORD` defaults to `PASSWD` if not provided
#        - name: NOVNC_ENABLE
#          value: "true"
        # Additional view-only password only applicable to the noVNC interface, choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: NOVNC_VIEWPASS
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: my-pass
#              key: my-pass
        ###
        # selkies-gstreamer parameters, for additional configurations see lines that start with "parser.add_argument" in https://github.com/selkies-project/selkies-gstreamer/blob/master/src/selkies_gstreamer/__main__.py
        ###
        # Change `WEBRTC_ENCODER` to `x264enc` if your GPU doesn't support `H.264 (AVCHD)` under the `NVENC - Encoding` section in https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new
        - name: WEBRTC_ENCODER
          value: "nvh264enc"
        - name: WEBRTC_ENABLE_RESIZE
          value: "false"
        - name: ENABLE_BASIC_AUTH
          value: "true"
        # Defaults to `PASSWD` if unspecified, choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: BASIC_AUTH_PASSWORD
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: my-pass
#              key: my-pass
        ###
        # Uncomment below to use a TURN server for improved network compatibility
        ###
        - name: TURN_HOST
          value: "turn.nrp-nautilus.io"
        - name: TURN_PORT
          value: "3478"
        # Provide only `TURN_SHARED_SECRET` for time-limited shared secret authentication or both `TURN_USERNAME` and `TURN_PASSWORD` for legacy long-term authentication, but do not provide both authentication methods at the same time
        - name: TURN_SHARED_SECRET
          valueFrom:
            secretKeyRef:
              name: my-pass
              key: turn-secret
#        - name: TURN_USERNAME
#          value: "username"
        # Choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: TURN_PASSWORD
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: turn-password
#              key: turn-password
        # Change to `tcp` if the UDP protocol is throttled or blocked in your client network, or when the TURN server does not support UDP
        - name: TURN_PROTOCOL
          value: "udp"
        # You need a valid hostname and a certificate from authorities such as ZeroSSL (Let's Encrypt may have issues) to enable this
        - name: TURN_TLS
          value: "false"
        stdin: true
        tty: true
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          limits:
            memory: 64Gi
            cpu: "16"
            nvidia.com/gpu: 1
          requests:
            memory: 100Mi
            cpu: 100m
        volumeMounts:
        - mountPath: /dev/shm
          name: dshm
        - mountPath: /cache
          name: xgl-cache-vol
        - mountPath: /home/user
          name: xgl-root-vol
      dnsPolicy: None
      dnsConfig:
        nameservers:
        - 8.8.8.8
        - 8.8.4.4
      volumes:
      - name: dshm
        emptyDir:
          medium: Memory
      - name: xgl-cache-vol
        emptyDir: {}
#        persistentVolumeClaim:
#          claimName: xgl-cache-vol
      - name: xgl-root-vol
        emptyDir: {}
#        persistentVolumeClaim:
#          claimName: xgl-root-vol
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: NotIn
                values:
                - ucsd-suncave
#              - key: topology.kubernetes.io/region
#                operator: In
#                values:
#                - us-west

The below is a reference configuration egl.yml for docker-nvidia-egl-desktop.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: egl
spec:
  replicas: 1
  selector:
    matchLabels:
      app: egl
  template:
    metadata:
      labels:
        app: egl
    spec:
      hostname: egl
      containers:
      - name: egl
        image: ghcr.io/selkies-project/nvidia-egl-desktop:latest
        env:
        - name: TZ
          value: "UTC"
        # Keep to default unless you know what you are doing with VirtualGL, `VGL_DISPLAY` should be set to either `egl[n]`, or `/dev/dri/card[n]` only when the device was passed to the container
#        - name: VGL_DISPLAY
#          value: "egl"
        - name: SIZEW
          value: "1920"
        - name: SIZEH
          value: "1080"
        - name: REFRESH
          value: "60"
        - name: DPI
          value: "96"
        - name: CDEPTH
          value: "24"
        # Choose either `value:` or `secretKeyRef:` but not both at the same time
        - name: PASSWD
#          value: "mypasswd"
          valueFrom:
            secretKeyRef:
              name: my-pass
              key: my-pass
        # Uncomment this to enable noVNC, disabing selkies-gstreamer and ignoring all its parameters except `BASIC_AUTH_PASSWORD`, which will be used for authentication with noVNC, `BASIC_AUTH_PASSWORD` defaults to `PASSWD` if not provided
#        - name: NOVNC_ENABLE
#          value: "true"
        # Additional view-only password only applicable to the noVNC interface, choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: NOVNC_VIEWPASS
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: my-pass
#              key: my-pass
        ###
        # selkies-gstreamer parameters, for additional configurations see lines that start with "parser.add_argument" in https://github.com/selkies-project/selkies-gstreamer/blob/master/src/selkies_gstreamer/__main__.py
        ###
        # Change `WEBRTC_ENCODER` to `x264enc` if you are using software fallback without allocated GPUs or your GPU doesn't support `H.264 (AVCHD)` under the `NVENC - Encoding` section in https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new
        - name: WEBRTC_ENCODER
          value: "nvh264enc"
        - name: WEBRTC_ENABLE_RESIZE
          value: "false"
        - name: ENABLE_BASIC_AUTH
          value: "true"
        # Defaults to `PASSWD` if unspecified, choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: BASIC_AUTH_PASSWORD
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: my-pass
#              key: my-pass
        ###
        # Uncomment below to use a TURN server for improved network compatibility
        ###
        - name: TURN_HOST
          value: "turn.nrp-nautilus.io"
        - name: TURN_PORT
          value: "3478"
        # Provide only `TURN_SHARED_SECRET` for time-limited shared secret authentication or both `TURN_USERNAME` and `TURN_PASSWORD` for legacy long-term authentication, but do not provide both authentication methods at the same time
        - name: TURN_SHARED_SECRET
          valueFrom:
            secretKeyRef:
              name: my-pass
              key: turn-secret
#        - name: TURN_USERNAME
#          value: "username"
        # Choose either `value:` or `secretKeyRef:` but not both at the same time
#        - name: TURN_PASSWORD
#          value: "mypasswd"
#          valueFrom:
#            secretKeyRef:
#              name: turn-password
#              key: turn-password
        # Change to `tcp` if the UDP protocol is throttled or blocked in your client network, or when the TURN server does not support UDP
        - name: TURN_PROTOCOL
          value: "udp"
        # You need a valid hostname and a certificate from authorities such as ZeroSSL (Let's Encrypt may have issues) to enable this
        - name: TURN_TLS
          value: "false"
        stdin: true
        tty: true
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          limits:
            memory: 64Gi
            cpu: "16"
            nvidia.com/gpu: 1
          requests:
            memory: 100Mi
            cpu: 100m
        volumeMounts:
        - mountPath: /dev/shm
          name: dshm
        - mountPath: /cache
          name: egl-cache-vol
        - mountPath: /home/user
          name: egl-root-vol
      dnsPolicy: None
      dnsConfig:
        nameservers:
        - 8.8.8.8
        - 8.8.4.4
      volumes:
      - name: dshm
        emptyDir:
          medium: Memory
      - name: egl-cache-vol
        emptyDir: {}
#        persistentVolumeClaim:
#          claimName: egl-cache-vol
      - name: egl-root-vol
        emptyDir: {}
#        persistentVolumeClaim:
#          claimName: egl-root-vol
#      affinity:
#        nodeAffinity:
#          requiredDuringSchedulingIgnoredDuringExecution:
#            nodeSelectorTerms:
#            - matchExpressions:
#              - key: topology.kubernetes.io/region
#                operator: In
#                values:
#                - us-west

Customization

In one entry, value: and valueFrom must not exist at the same time.

Comment out emptyDir: {} and uncomment persistentVolumeClaim:, then change claimName: to the name of your PersistentVolumeClaim after uncommenting.

rook-ceph-block, rook-ceph-block-east, or rook-ceph-block-pacific are the recommended StorageClasses to be mounted to /home/user. However, if performance is slow you may use rook-cephfs, rook-cephfs-east, rook-cephfs-pacific, seaweedfs-storage-hdd, or seaweedfs-storage-nvme, but NEVER MOUNT TO /home/user. Instead mount to a different directory such as /home/user/persistent or /mnt/persistent. Refer to the POSIX section for more information.

If your client network blocks (likely your campus network) or throttles (likely your home network restricted by your Internet Service Privider) the UDP protocol, change the environment variable TURN_PROTOCOL to tcp.

If your network or country enforces Deep Packet Inspection to the WebRTC or STUN/TURN protocols, set TURN_TLS to true.

Check the README.md of both GitHub repositories docker-nvidia-glx-desktop or docker-nvidia-egl-desktop for more details on configuration customization. Please give the repositories a star too!

Secret Generation

Before generating the Kubernetes secret, receive the TURN Shared Secret by asking in the Nautilus Support Matrix Chatroom, and replace TURN_SHARED_SECRET to the value that you have received. Replace YOUR_PASSWORD with the password for the container that you intend to use. If you want to use a different name instead of my-pass or turn-secret, make sure to update the above xgl.yml or egl.yml files as well as the below command. The first my-pass is the name of the secret, and the second my-pass right after --from-literal= and turn-secret are the keys.

After customizing, run the below command in edited form:

kubectl create secret generic my-pass --from-literal=my-pass=YOUR_PASSWORD --from-literal=turn-secret=TURN_SHARED_SECRET

Container Start

After saving and editing the reference configurations, run either of these commands to start the container depending on the type of your container:

kubectl create -f xgl.yml

kubectl create -f egl.yml

Exposing the Container

The below reference configuration xgl-ingress.yml is to expose your docker-nvidia-glx-desktop container to the *.nrp-nautilus.io endpoint. Replace YOUR_ENDPOINT to the subdomain you want to use. Modify the configuration as in Scaling and exposing to customize when there are multiple desktop deployments in a namespace. You can just use kubectl port-forward deployment/xgl 8080:8080 and access localhost:8080, but this will have higher latency and subpar performance.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: haproxy
  name: xgl
spec:
  rules:
  - host: YOUR_ENDPOINT.nrp-nautilus.io
    http:
      paths:
      - backend:
          service:
            name: xgl
            port:
              name: http
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - YOUR_ENDPOINT.nrp-nautilus.io
---
apiVersion: v1
kind: Service
metadata:
  name: xgl
  labels:
    app: xgl
spec:
  selector:
    app: xgl
  ports:
  - name: http
    protocol: TCP
    port: 8080

Run the below command after saving the changed reference configuration file: kubectl create -f xgl-ingress.yml

Access YOUR_ENDPOINT.nrp-nautilus.io with your web browser.

The below reference configuration egl-ingress.yml is to expose your docker-nvidia-egl-desktop container to the *.nrp-nautilus.io endpoint. Replace YOUR_ENDPOINT to the subdomain you want to use. Modify the configuration as in Scaling and exposing to customize when there are multiple desktop deployments in a namespace. You can just use kubectl port-forward deployment/egl 8080:8080 and access localhost:8080, but this will provide higher latency and subpar performance.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: haproxy
  name: egl
spec:
  rules:
  - host: YOUR_ENDPOINT.nrp-nautilus.io
    http:
      paths:
      - backend:
          service:
            name: egl
            port:
              name: http
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - YOUR_ENDPOINT.nrp-nautilus.io
---
apiVersion: v1
kind: Service
metadata:
  name: egl
  labels:
    app: egl
spec:
  selector:
    app: egl
  ports:
  - name: http
    protocol: TCP
    port: 8080

Run the below command after saving the changed reference configuration file: kubectl create -f egl-ingress.yml

Access YOUR_ENDPOINT.nrp-nautilus.io with your web browser. The user name is "user" without the quotes and the password is the my-pass secret that you have set.

Reproducibility in Containers

Reproducibility is important, but the latest and 2*.04 containers are consistently updated to fix various issues that arise and add improvements. Because some changes are radical and change the fundamental structure of the container (normally when there is a dead-end with breaking issues), they might break for your specific workflow.

Since you would not want to see your container break one day because there was a change, you could use persistent container tags (in the format 22.04-20240101010101) with the time which a commit was made, if you want to base this container to build a customized container. Because the containers have rolling releases, container versions are tagged by the time of which a commit has been made.

You may access such persistent container tags in https://github.com/selkies-project/docker-nvidia-glx-desktop/pkgs/container/nvidia-glx-desktop and https://github.com/selkies-project/docker-nvidia-egl-desktop/pkgs/container/nvidia-egl-desktop.

It is STRONGLY recommended that you update the container tags frequently or use apt-get upgrade in every new build where this container has been based (but this may also break the container in a few cases), as many serious security vulnerabilities are frequently present in old containers. Completely fresh builds from the Dockerfile with older commits may and will break at any time.

Container Customization

You can import either https://github.com/selkies-project/docker-nvidia-glx-desktop.git or https://github.com/selkies-project/docker-nvidia-egl-desktop.git by importing with Repo by URL from https://gitlab.nrp-nautilus.io/projects/new#import_project.

Refer to Building in GitLab on how you can change and build your own customized container (add --build-arg="UBUNTU_RELEASE=<Ubuntu Version>" after /kaniko/executor in the example .gitlab-ci.yml file to change the Ubuntu version).

If you are changing the Dockerfile, you are recommended to use the containers as a base container and only replace the entrypoint.sh and supervisord.conf files. This will keep you up to date with the latest updates.

To do this, start with these sample Dockerfile examples and place your modified entrypoint.sh and supervisord.conf files within the same git directory:

ARG UBUNTU_RELEASE=22.04
FROM ghcr.io/selkies-project/nvidia-glx-desktop:${UBUNTU_RELEASE}
ARG UBUNTU_RELEASE

USER root
# Install and configure below this line

# Replace changed files
#COPY entrypoint.sh /etc/entrypoint.sh
#RUN chmod 755 /etc/entrypoint.sh
#COPY supervisord.conf /etc/supervisord.conf
#RUN chmod 755 /etc/supervisord.conf

EXPOSE 8080

USER user
ENV USER=user
WORKDIR /home/user

ENTRYPOINT ["/usr/bin/supervisord"]

or

ARG UBUNTU_RELEASE=22.04
FROM ghcr.io/selkies-project/nvidia-egl-desktop:${UBUNTU_RELEASE}
ARG UBUNTU_RELEASE

USER root
# Install and configure below this line

# Replace changed files
#COPY entrypoint.sh /etc/entrypoint.sh
#RUN chmod 755 /etc/entrypoint.sh
#COPY supervisord.conf /etc/supervisord.conf
#RUN chmod 755 /etc/supervisord.conf

EXPOSE 8080

USER user
ENV USER=user
WORKDIR /home/user

ENTRYPOINT ["/usr/bin/supervisord"]

Again, please give both docker-nvidia-glx-desktop and docker-nvidia-egl-desktop a star if the containers were useful to you.