This guide will show you how to run a GUI application headless in a Docker container and even more specific scenarios involving running Firefox and Chrome. If you are not interested about those then you can just stop in the middle of this tutorial.
What the hell is X?
X is a program that sits on a Linux machine with a monitor (so servers usually don’t use X). X’s job is to talk to the Linux kernel in behalf of GUI programs. So if you are playing a game for example, the game (that is, the application) is constantly sending drawing commands to the X server like “draw me a rectangle here”. X forwards all this to the Kernel which will further forward the information to the GPU to render it on the monitor.
X can even receive commands from the keyboard or mouse. When you click to shoot on your game for example, the command “click at 466,333” is sent from your mouse to the kernel, from the kernel to the X and from X to the game. That way the game can have a clue on what is happening!
You will often hear X being called a server and the reason for that is simply because the way the applications send commands to it is through sockets. For that reason the applications are also referred to as clients many times.
If you are reading this then the X is running on your PC. Let’s prove it:
> ps aux | grep X root 1436 3.2 0.7 687868 94444 tty7 Ssl+ 09:47 2:50 /usr/bin/X -core :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
We can see that X is running as root and has PID 1436. An other important thing is to notice the :0 which is called display in X jargon. A display is essentially:
- A monitor
- A mouse
- A keyboard
And this is the bigger picture of how it all looks together:
Now there is a variable in Linux that is used whenever we run a GUI program. That variable is surprisingly called DISPLAY. The syntax of the DISPLAY variable is
<hostname>:<display>.<monitor>
. Let’s check the DISPLAY on our computer:
> echo $DISPLAY :0
I get :0, which means we use display 0. Notice however that this says nothing about which monitor we use. This makes sense since if you are running 2 or more monitors on your Linux you still have the same environment variables in both of them. It wouldn’t make sense that an environment variable changes just because you echo it from a different screen, would it? For that reason we get the display and not the monitor so that we get the same output on both. As about the hostname, since there is no info about it, the local host is assumed.
On a notice, if you have multiple monitors you can still specify which monitor to run an application by simply typing the full display variable you want. So if you have a monitor 0 and a monitor 1 on the current display, I can run firefox on monitor 1 with:
DISPLAY=:0.1 firefox
Creating a virtual monitor
Instead of running X, we can run a different version of it that can create virtual displays. Xvfb (virtual framebuffer – whatever the hell that means) will create a virtual monitor for us.
So let’s make a new monitor (I assume you have installed xvfb):
Xvfb :1 -screen 0 1024x768x16
This will start the Xvfb server with a display 1 and a virtual screen(monitor) 0. We can access this by simply typing DISPLAY=:1.0
before running our graphical program. In this case the program will start in the virtual screen instead of our monitor.
Let’s make sure that the screen is still running:
> ps aux | grep X root 1436 3.1 0.7 684580 91144 tty7 Ssl+ 09:47 3:31 /usr/bin/X -core :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch manos 22018 0.0 0.1 164960 20756 pts/27 Sl+ 11:37 0:00 Xvfb :1 -screen 0 1024x768x16
We see we have the normal display 0. (A way to tell it is the default screen is to see that it runs as root.) We can also see the second display :1 and screen 0 with resolution 1024×768. So what if we want to use it?
Open a new terminal and type:
> DISPLAY=:1.0 firefox ..
This will start firefox at the given display. The reason I use the DISPLAY at the same line is to make sure that the subprocess inherits the variable DISPLAY. An other way to do this is to type:
> DISPLAY=:1.0 > export DISPLAY > firefox ..
Run a GUI program in a Docker container
We will now create a virtual screen inside a docker container.
> docker run -it ubuntu bash root@660ddd5cc806:/# apt-get update root@660ddd5cc806:/# apt-get install xvfb root@660ddd5cc806:/# Xvfb :1 -screen 0 1024x768x16 &> xvfb.log & root@660ddd5cc806:/# ps aux | grep X root 11 0.0 0.1 169356 20676 ? Sl 10:49 0:00 Xvfb :1 -screen 0 1024x768x16
So now we are sure that we are running the virtual screen. Let’s access it and run something graphical on it. In this case I will run Firefox and Python+Selenium just as a proof of concept of what is happening.
First I put my display variable and use export to assure that any sub-shells or sub-processes use the same display (with export, they inherit the variable DISPLAY!):
root@660ddd5cc806:/# DISPLAY=:1.0 root@660ddd5cc806:/# export DISPLAY
Now we can simply run a browser
root@660ddd5cc806:/# firefox (process:14967): GLib-CRITICAL **: g_slice_set_config: assertion 'sys_page_size == 0' failed Xlib: extension "RANDR" missing on display ":99.0". (firefox:14967): GConf-WARNING **: Client failed to connect to the D-BUS daemon: //bin/dbus-launch terminated abnormally without any error message ..
The errors don’t mean anything. But we can’t be sure, can we? I mean, since we can’t see what’s happening it’s really hard to tell. There are two things we can do, either use ImageMagick to take a snapshot and send it to our host via a socket or we can simply use Selenium. I will do that since most people probably want to achieve all this for testing purposes anyway.
root@0e395f0ef30a:/# apt-get install python-pip root@0e395f0ef30a:/# pip install selenium root@0e395f0ef30a:/# python
>>> from selenium import webdriver >>> browser=webdriver.Firefox() >>> browser.get("http://www.google.com") >>> browser.page_source
If you get a bunch of HTML, then we have succeeded!
The Chrome issue
If you try and run Chrome in a Docker container, it won’t work even if you have setup everything correctly. The reason is that Chrome uses something called sandboxing. Reading this I could not let but notice the word jail. Apparently it seems that Chrome uses Linux containers (the same that Docker uses). For this reason you have to put a bit of extra effort to solve this issue since because of technical difficulties it’s not possible to run containers in containers.
There are two workarounds:
- Use my docker-enter
- Use –privileged when running the container
The second solution is probably the best one. However while testing things, there’s nothing wrong with the first one.
So to make things work (notice I run everything from the start):
> docker run -it --privileged ubuntu bash root@7dd2c07cb8cb:/# apt-get update root@7dd2c07cb8cb:/# apt-get install wget python-pip xvfb root@7dd2c07cb8cb:/# wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - OK root@7dd2c07cb8cb:/# echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list root@7dd2c07cb8cb:/# apt-get update root@7dd2c07cb8cb:/# apt-get install google-chrome-stable root@7dd2c07cb8cb:/# pip install selenium
I have now installed Selenium, Chrome and Xvfb. Now I am going to make make a virtual monitor and run Chrome:
root@7dd2c07cb8cb:/# Xvfb :99 -screen 0 1024x768x16 &> xvfb.log & [1] 6729 root@7dd2c07cb8cb:/# DISPLAY=:99.0 root@7dd2c07cb8cb:/# export DISPLAY root@7dd2c07cb8cb:/# google-chrome Xlib: extension "RANDR" missing on display ":99.0". Xlib: extension "RANDR" missing on display ":99.0". [6736:6736:1017/143449:ERROR:desktop_window_tree_host_x11.cc(802)] Not implemented reached in virtual void views::DesktopWindowTreeHostX11::InitModalType(ui::ModalType) ATTENTION: default value of option force_s3tc_enable overridden by environment. failed to create drawable [6775:6775:1017/143449:ERROR:gl_surface_glx.cc(633)] glXCreatePbuffer failed. [6775:6775:1017/143449:ERROR:gpu_info_collector.cc(27)] gfx::GLContext::CreateOffscreenGLSurface failed [6775:6775:1017/143449:ERROR:gpu_info_collector.cc(89)] Could not create surface for info collection. [6775:6775:1017/143449:ERROR:gpu_main.cc(402)] gpu::CollectGraphicsInfo failed (fatal). [6775:6775:1017/143449:ERROR:sandbox_linux.cc(305)] InitializeSandbox() called with multiple threads in process gpu-process [6775:6775:1017/143449:ERROR:gpu_child_thread.cc(143)] Exiting GPU process due to errors during initialization [6736:6736:1017/143449:ERROR:gpu_process_transport_factory.cc(418)] Failed to establish GPU channel.
It seems that it works. It’s normal that we get the gpu errors since we don’t have a gpu! However I don’t like gambling so we will take it a step further to check that the browser actually works. However for this I will need to download the webdriver for Google Chrome..
root@7dd2c07cb8cb:/# apt-get install curl unzip root@7dd2c07cb8cb:/# cpu_arch=$(lscpu | grep Architecture | sed "s/^.*_//") root@7dd2c07cb8cb:/# version=$(curl 'http://chromedriver.storage.googleapis.com/LATEST_RELEASE' 2> /dev/null) root@7dd2c07cb8cb:/# url_file="chromedriver_linux${cpu_arch}.zip" root@7dd2c07cb8cb:/# url_base="http://chromedriver.storage.googleapis.com" root@7dd2c07cb8cb:/# url="${url_base}/${version}/${url_file}" root@7dd2c07cb8cb:/# wget "$url" root@7dd2c07cb8cb:/# unzip chromedriver_*.zip -d tmp root@7dd2c07cb8cb:/# mv tmp/chromedriver usr/bin/
Now (FINALLY!) we can test with Selenium:
root@7dd2c07cb8cb:/# python >>> from selenium import webdriver >>> browser=webdriver.Chrome() >>> browser.get("http://en.wikipedia.org/wiki/Open_source") >>> browser.page_source
You should get a bunch of HTML code. So there we go!
Common errors
.. Gtk: cannot open display:
DISPLAY has wrong value or you forgot to export it!
References
http://www.x.org/wiki/Development/Documentation/HowVideoCardsWork/
http://www.x.org/archive/X11R7.7/doc/man/man1/Xvfb.1.xhtml
http://blog.mecheye.net/2012/06/the-linux-graphics-stack/
Click to access linuxgraphicsdrivers.pdf
http://www.tldp.org/HOWTO/Framebuffer-HOWTO/
http://en.wikipedia.org/wiki/X_Window_System
http://en.wikipedia.org/wiki/Framebuffer
http://en.wikipedia.org/wiki/X.Org_Server
http://en.wikipedia.org/wiki/Display_server
http://www.google.com/googlebooks/chrome/med_26.html
http://linux.die.net/man/1/xvfb
https://www.freebsd.org/doc/handbook/x-understanding.html
October 22, 2014 at 2:41 pm
I blogged about this a while ago here:
http://zwischenzugs.wordpress.com/?s=2048
as a proof of concept, I played 2048 within a container using VNC.
October 22, 2014 at 3:27 pm
That is different though. You used the monitor on your local machine. I use a *virtual* monitor inside the container itself. No VNC!
October 22, 2014 at 3:53 pm
You’re right of course. I should have said “a similar thing” rather than “this”. Your way is neater.
On Wed, Oct 22, 2014 at 4:27 PM, LinuxMeerkat wrote:
> aquameerkat commented: “That is different though. You used the monitor > on your local machine. I use a *virtual* machine inside the container > itself. No VNC!”
November 25, 2014 at 12:49 pm
Thanks a lot for the detailed post, it was really insightful! I especially appreciated the fact you posted link to resources you used, that’s a must.
I stumbled upon this post looking for a method to run GUI applications in a container/VM. I’ve already done it through systemd-nspawn and using xhost +local: (see https://wiki.archlinux.org/index.php/Chroot#Run_graphical_applications_from_chroot), however I was wondering about the possibility to do so in Docker…
For anybody looking for the same info, many interestings leads can be found in this StackOverflow question: https://stackoverflow.com/questions/16296753/can-you-run-gui-apps-in-a-docker-container
Cheers
March 13, 2015 at 3:30 pm
Thank you for the post, it is amazing. It saved me a day and cleared my mind from fear of «what the hell are these virtual displays and how should I live with them».
March 13, 2015 at 9:01 pm
Thank you. It’s nice to hear that someone got something out of it 🙂
June 26, 2015 at 4:57 am
I tried your way and yes it works! Thanks!
But I met this problem while trying to run my test using the chrome web driver, do you have any idea about how to fix this?
Failure/Error: Unable to find matching line from backtrace
Selenium::WebDriver::Error::UnknownError:
unknown error: session deleted because of page crash
from tab crashed
(Session info: chrome=43.0.2357.130)
(Driver info: chromedriver=2.14.313457 (3d645c400edf2e2c500566c9aa096063e707c9cf),platform=Linux 4.0.3-boot2docker x86_64)
Thank you so much!
Pingback: Containerizing the W3C Mobile Checker App | Damnhandy
July 31, 2015 at 12:38 am
Excuse me for being a noob but how can I use this top open up a firefox window on my desktop and begin browsing the web? I like the idea of containerising applications but it’s not immediately obvious to how to achieve this from the above tutorial? Thanks and great quick tutorial on X by the way 🙂
July 31, 2015 at 10:33 am
In that case you would need to forward the X commands back and forth from your host machine. Check this link: http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/
Pingback: Running QtCreator in Docker | Geek w/ god complex
January 14, 2016 at 7:19 pm
Have you ever experienced a docker container getting hung up when using a virtual frame buffer? I am trying to dockerize IBM IIB toolkit to build BAR files. The one command I use, runs eclipse in headless mode. I added a entrypoint script that runs the frame buffer in background like this: echo | Xvfb $DISPLAY -screen 0 1024x768x16 & I cant run the virtual buffer in the build from the Dockerfile. Needs to happen at run time. But randomly the container gets hungup and won’t quit. I suspect it is due to a process still running, and I am assuming the virtual framebuffer is the culprit.
January 15, 2016 at 4:42 pm
Thank you for this very nice solution ! So quick and easy ! It works very well ! You saved me time and my mind ! 😉
February 9, 2016 at 2:31 pm
I have a Virtual-box VM. By ssh’ing into the VM, I run docker image consisting of selenium server within it. Now my selenium server is up and running.I have test script running on Eclipse, in host machine. I need to test selenium against by local browser. How can i do that ??
February 9, 2016 at 8:47 pm
Can your docker box contain a copy of Windows-64-XP that can run on linux?
I want to run Windows Desktop Search that isn’t available on Win7. The search on Win7 will only index “local content”. My “data” (email, documents, pictures, music, etc…) is all stored on a
relatively cheap linux server. I can back up the data, daily and reconstruct the system from daily backups (something MS stopped providing in Home versions of Windows from Vista on).
What MS’s response has been is that remote content on Windows can only be accessed and indexed on the remote machine, though your local machine can access remote indices via the included MS-search — which can search through indexed material available on a MS server. (only).
So for me, the only thing I try to put on my Windows machine are Windows programs, which can be backed up if they are on the same disk as the Operating System. I.e.: If you have a separate disk for your personal documents and programs, the programs *won’t* be backed up.
If the programs are on the system disk, Windows backup has an alternate backup-method that
only allows you creating an “image” backup — and even the MS-image backup won’t backup
many types of licenses or encrypted data. The official way to index remote content, referred to MS in
questions on their Win7 community site is to run a copy of WinXP and run the old Desktop search which does do remote indexing and provides it in the same format as used by the new MS-search.
Thus — my want to run a VM – (docker?, other?) on my linux server just for the indexing. Is that possible?
Thanks!
Linda
Pingback: Ubuntu Desktop image | Docker Faq
July 25, 2016 at 11:33 am
Thanks, really useful article ! I was struggling to run my chimp tests from within a container and was missing the –privileged option to have chrome running within the container and using Xvfb.
Pingback: Running (and recording) fully automated GUI tests in the cloud – GREENSTACK
Pingback: Dockers and Linux Containers 101 - Rouge Neuron
August 20, 2018 at 7:07 am
I am truly thankful to thee holder of this web page
who has shared this impressive piece of writing at at this place.
October 9, 2019 at 9:52 am
Il est en effet spécial pour moi, mais aussi Thiago et pour mon personnel.