Docker, Qt, Vulkan, and CI
Docker, Qt, and Vulkan walk into a project with Github Actions CI. They don’t get along. The crux of the issue is that the default-provided Github Actions Runners (machines/VMs) have neither displays nor GPUs. A display would typically be required for running Qt GUI applications, and a GPU would typically be required for running Vulkan applications. Why would I want to run a GUI application with graphics rendering in an environment like this? Simple: I want to run my unit tests upon code check-in.
As a note before I begin, my development environment is Archlinux and so that is also what I use for my CI environment. I have found several times that Ubuntu just doesn’t have packages that are up-to-date enough for my projects.
Qt GUI in Headless Environment
The first solution I ran across, which is probably the easiest if you are only working with Qt, is to simply set a Qt environment variable before running your tests:
export QT_QPA_PLATFORM=offscreen
This works great and allows Qt to run without a display. Unfortunately, the Vulkan interop is not supported when using this option. Another solution, which does satisfy all of my constraints, is to create a ‘virtual display’ so that Qt thinks that it isn’t on a headless system. To do this, install xorg-server-xvfb. XVFB stands for X(org) Virtual FrameBuffer. Then to start the virtual display run the following:
Xvfb :1 -screen 0 1920x1080x16& export DISPLAY=:1.0
Vulkan Software Rendering
Getting Vulkan to work without a GPU is actually easier than getting Qt to work without a display. All you really need is to install the vulkan-swrast package. Viola. This package exposes a Vulkan driver which translates calls into CPU commands instead of GPU commands.
And Docker?
The steps above don’t necessarily NEED Docker. You could likely get all of it working on Github’s Ubuntu runners. The reason I use Docker is so that I can get my CI environment to match my development environment. This prevents the classic ‘it builds fine on my machine’ issue, because essentially all the machines building the project are the same. You can even integrate Docker into some IDEs so that your project will get built and run within the Docker container. This ensures that you always have the correct development environment.
The reason I made the move to using Docker in my CI was simply that I couldn’t get new enough packages on Ubuntu. I needed an updated version of CMake to use the qt6_standard_project_setup() routine. I could either wait months for the next Ubuntu release to include that package, or I could set up a Docker image and get exactly the tool versions that I needed.
The above steps do fit very well with Docker, though, because they work with vanilla Docker. You don’t need to use the —privileged flag when starting the container. You don’t need to use any of the NVIDIA-Docker layers to give the container access to your GPU. It just works. It’ll work much slower than using a GPU, but it will work fine for running tests in a CI environment.