Can you run GUI applications in a Linux Docker container?
Asked Answered
T

24

508

How can you run GUI applications in a Linux Docker container?

Are there any images that set up vncserver or something so that you can - for example - add an extra speedbump sandbox around say Firefox?

Tartuffery answered 30/4, 2013 at 9:40 Comment(2)
Related: How to make Xvfb display visible?Ponder
Check HPC Visualization Containers User Guide for some ideas.Ponder
A
290

You can simply install a vncserver along with Firefox :)

I pushed an image, vnc/firefox, here: docker pull creack/firefox-vnc

The image has been made with this Dockerfile:

# Firefox over VNC
#
# VERSION               0.1
# DOCKER-VERSION        0.2

FROM    ubuntu:14.04
# Make sure the package repository is up to date
RUN     apt-get update

# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN     apt-get install -y x11vnc xvfb firefox
RUN     mkdir ~/.vnc
# Setup a password
RUN     x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way to do it, but it does the trick)
RUN     bash -c 'echo "firefox" >> /.bashrc'

This will create a Docker container running VNC with the password 1234:

For Docker version 18 or newer:

docker run -p 5900:5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create

For Docker version 1.3 or newer:

docker run -p 5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create

For Docker before version 1.3:

docker run -p 5900 creack/firefox-vnc x11vnc -forever -usepw -create
Antemortem answered 1/5, 2013 at 0:55 Comment(20)
How would I use a VNC client to view this remotely? Typing in the IP + port doesn't seem to be working.Areca
First, you need to check the port allocated (by doing docker inspect <container id> or simply docker ps, then you connect to your host's ip with the port you just found.Antemortem
You can see the equivalent 5900 port with this command docker ps | grep -Po ':\K[0-9]*'Disappointed
the creackfirefox-vnc image fails with error: Enter VNC password: stty: standard input: Inappropriate ioctl for device fgets: No such file or directory stty: standard input: Inappropriate ioctl for device x11vnc -usepw: could not find a password to use.Brashy
Use docker well > Running GUI apps with Docker fabiorehm.com/blog/2014/09/11/running-gui-apps-with-dockerTropical
How can I find the firefox window id and share only the firefox window? x11vnc has the -id <window> option but I don't know how to find the id. A terminal window is also opened and I'm not sure why.Dreamadreamer
Not clear what is the username and password. Which vnc client to use and how to connect?Cauca
There is no username, the password is clearly indicated in the answer and any vnc client will do. In my case, I like the native osx one. (from finder, press command+K and connect to vnc://<docker ip>:<container exposed port>)Antemortem
Is there a way to change the screen resolution. When I used this image it uses only 50% of the real estate.Manse
I think 12.04 is not supported anymore. If you bump to 14.04 it should work. Might need to update the universe repo as well.Antemortem
I found the port the vnc server mapped to, and the docker version I am using is (on ubuntu 14.04) 1.10.2. When I run vncviewer localhost:32776 -passwd ~/.vnc/passwdfile I do not see firefox, but i see Thu Feb 25 07:07:31 2016 CConn: connected to host localhost port 32776 main: End of stream Can anyone help please?Detestable
@Antemortem awesome works, but please, please, please include run example in Dockerfile and on your github, or at least link to this stackoverflow question.Meyeroff
Done. Didn't test if still working though. hub.docker.com/r/creack/firefox-vncAntemortem
for centos version see: github.com/CentOS/CentOS-Dockerfiles/blob/master/firefox/…Rattat
I seemed to need -t in docker run to get a password prompt. I'm not sure why a password is even needed in this. Anyway, please update the instructions by editing the original instructions rather than adding "EDIT" sections.Diopside
I changed this Dockerfile to use .xinitrc instead of .bashrc: RUN echo "exec firefox" > ~/.xinitrc && chmod +x ~/.xinitrc. x11vnc -create ... opens a new display and tries to run .xinitrcLoats
To run a desktop in docker under vnc ConSol has a nice solution.Fustian
This will probably work everywhere. But If the host is Linux with X11, this is overkill. If the host is Linux with X11, X11 based solutions will be more light-weight.Hesperidin
Your Dockerfile instructions don't working, be cause I build the instruction and can't start de container :( # nano Dockerfile (and put the information) # docker build -t mycontainervnc . # docker run -p 5900:5900 --name vnc -e HOME=/ mycointainervnc x11vnc -forever -usepw -create # and not working :(Lechner
Example as written no longer works due to out of date Ubuntu version. Precise (12.04) package repository no longer exists.Argyres
T
218

Xauthority becomes an issue with newer systems. I can either discard any protection with xhost + before running my docker containers, or I can pass in a well prepared Xauthority file. Typical Xauthority files are hostname specific. With docker, each container can have a different host name (set with docker run -h), but even setting the hostname of the container identical to the host system did not help in my case. xeyes (I like this example) simply would ignore the magic cookie and pass no credentials to the server. Hence we get an error message 'No protocol specified Cannot open display'

The Xauthority file can be written in a way so that the hostname does not matter. We need to set the Authentication Family to 'FamilyWild'. I am not sure, if xauth has a proper command line for this, so here is an example that combines xauth and sed to do that. We need to change the first 16 bits of the nlist output. The value of FamilyWild is 65535 or 0xffff.

docker build -t xeyes - << __EOF__
FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes
__EOF__
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
xauth nlist :0 | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
docker run -ti -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH xeyes
Toulouse answered 13/8, 2014 at 7:33 Comment(16)
Just a note, -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH can be shortened to -v $XSOCK -v $XAUTHPate
@PiotrAleksanderChmielowski that did not work for me, Docker version 1.12.0, build 8eab29eLounge
@Lounge - interesting. Maybe it is a bug to report?Pate
Does not work for me either: Error: Can't open display: :0 Docker v1.8.2-7 on Fedora 22.Barboza
@Dirk: You might want to replace :0 with $DISPLAY. That means xauth nlist $DISPLAY | ... and docker run -ti -e DISPLAY=$DISPLAY .... Usually the X DISPLAY is :0, but not always (and especially not if you are connecting via ssh -X).Gaullism
Just for people landing here: @PiotrAleksanderChmielowski comment did not work for me, and I also had to add --net=hostLombard
On Ubuntu 16.04 xauth creates the /tmp/.docker.xauth file with 600 permissions. This results in xauth inside docker container not being able to read the file. You can verify by running xauth list within the docker container. I have added chmod 755 $XAUTH after the xauth nlist :0 | ... command to resolve this.Rumble
@Rumble Why using 755, if 444 or 644 is enough?Erythema
How can I make this work on Windows 7? Since, it does not have X server capability?Gunnar
@walksignison, Install X server. There are three options: VcXsrv, Xming and Cygwin/X. xauth acts up a bit for me though; due to lack of unix domain sockets, the DISPLAY ends up being 127.0.0.1:0 and xauth does not accept it in some commands.Corpsman
@janhudec I was able to get it to work with Moba XTerm! Thanks a lot anyway!Gunnar
This does not work in the latest versions of docker. Docker refuses to mount from volumes where the host has the sticky bit set.Hesperidin
The following worked for me (in particular, adding -e DISPLAY=$DISPLAY), replacing the last four lines with: xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f /tmp/.docker.xauth nmerge - && docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /tmp/.docker.xauth:/tmp/.docker.xauth -e XAUTHORITY=/tmp/.docker.xauth -e DISPLAY=$DISPLAY xeyesBullyrag
@Rumble Good point! However you need chmod before xauth nlist ...Excogitate
running this over ssh, I had to add "--network host" to the docker run command as well. (to avoid "Error: Can't open display: localhost:10.0")Harrison
I didn't have a ~/.Xauthority file for some reason, so I had to generate one by running xauth generate $DISPLAY . trustedTonytonya
G
87

I just found this blog entry and want to share it here with you because I think it is the best way to do it and it is so easy.

http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/

PROS:
+ no x server stuff in the docker container
+ no vnc client/server needed
+ no ssh with x forwarding
+ much smaller docker containers

CONS:
- using x on the host (not meant for secure-sandboxing)

in case the link will fail someday I have put the most important part here:
dockerfile:

FROM ubuntu:14.04

RUN apt-get update && apt-get install -y firefox

# Replace 1000 with your user / group id
RUN export uid=1000 gid=1000 && \
    mkdir -p /home/developer && \
    echo "developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash" >> /etc/passwd && \
    echo "developer:x:${uid}:" >> /etc/group && \
    echo "developer ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/developer && \
    chmod 0440 /etc/sudoers.d/developer && \
    chown ${uid}:${gid} -R /home/developer

USER developer
ENV HOME /home/developer
CMD /usr/bin/firefox

build the image:

docker build -t firefox .

and the run command:

docker run -ti --rm \
   -e DISPLAY=$DISPLAY \
   -v /tmp/.X11-unix:/tmp/.X11-unix \
   firefox

of course you can also do this in the run command with sh -c "echo script-here"

HINT: for audio take a look at: https://mcmap.net/q/46941/-run-apps-using-audio-in-a-docker-container

Gipps answered 10/3, 2015 at 18:28 Comment(9)
How can I do this on windows 7? Do I need to install an X server?Gunnar
As most answers here this applies only to unix I think - until windows supports X server window system.Gipps
Do you think it could work if I installed X server on windows or even bundled an X server into my Docker container?Gunnar
installing X server into your docker does not make much sense since this is what I try to avoid and you would still need to render it to the display then. One of the vnc answers may fit you in your case.Gipps
However I could imagine it may work with Xming or Cygwin/X. Please write a comment if you got it working.Gipps
Okay will try installing Xming on windows and then try this.. One thing Im confused about is that the docker toolbox for windows runs inside Boot2Docker, which is a stripped down version of Linux for the Docker daemon.. Do you think I could install X server on this and run it with the exact same flags as above?Gunnar
I was actually able to get it to work using MobaXTerm by using the Dockerfile as above and modifying the docker run command to mount /tmp/.X11-unix to /c:/Users/username and set the host IPv4 address for the DISPLAY environment variable..Gunnar
I think you also need to install in Dockerfile apt-get -y install sudo to create /etc/sudoers.d folder.Duisburg
it may also be necessary to allow connections to X from any host with $ xhost +Callup
C
60

With docker data volumes it's very easy to expose xorg's unix domain socket inside the container.

For example, with a Dockerfile like this:

FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes

You could do the following:

$ docker build -t xeyes - < Dockerfile
$ XSOCK=/tmp/.X11-unix/X0
$ docker run -v $XSOCK:$XSOCK xeyes

This of course is essentially the same as X-forwarding. It grants the container full access to the xserver on the host, so it's only recommended if you trust what's inside.

Note: If you are concerned about security, a better solution would be to confine the app with mandatory- or role-based-access control. Docker achieves pretty good isolation, but it was designed with a different purpose in mind. Use AppArmor, SELinux, or GrSecurity, which were designed to address your concern.

Coumarone answered 6/8, 2014 at 19:21 Comment(11)
You also need to allow access to the X Server from other hosts using a tool like xhost. To completely open it up use xhost + on the host.Sailplane
@Sailplane only xhost +local is necessary. It would be better to make the ~/.Xauthority file available in the container however, so it can authenticate itself.Coumarone
have you managed to get it working on a Mac (using boot2docker) ?Wexler
@KarlForner sorry, I've only got linux here.Coumarone
This was working rather nicely for me on an Ubuntu 14.04 laptop with docker 1.5 earlier; but is now failing for me on Ubuntu 15.04, docker 1.6.2, with the error Can't open display: :0. Any ideas?Huddersfield
I see my problem on 15.04 is solved by the XAuthority mapping listed in Jürgen Weigert's solution below.Huddersfield
@KarlForner, for OS X see kartoza.com/…. This is much easier.Israelisraeli
I used xhost +si:localuser:$USER to authorise just the user starting the container.Curling
@AryehLeibTaurog I think the trailing colon is missing in your command xhost +local:. Without the colon, the command failed on Ubuntu 16.04Disloyalty
Has anyone figured out X forwarding for windows?Gunnar
I followed the instructions given in medium.com/@SaravSun/… and it worked flawlessly with this command line : sudo docker run --net=host --env="DISPLAY" --volume="$HOME/.Xauthority:/root/.Xauthority:rw" xeyesBattleplane
R
28

You can also use subuser: https://github.com/timthelion/subuser

This allows you to package many gui apps in docker. Firefox and emacs have been tested so far. With firefox, webGL doesn't work though. Chromium doesn't work at all.

EDIT: Sound works!

EDIT2: In the time since I first posted this, subuser has progressed greatly. I now have a website up subuser.org, and a new security model for connecting to X11 via XPRA bridging.

Recapitulation answered 11/2, 2014 at 22:49 Comment(3)
Please note that subuser is still very new and relatively untested. If you run into any problems please submit bug reports!Recapitulation
I'd avoid X11 if there's any way you can. Your killer app would be running the tor proxy in docker, and running a full browser with plugins in a child docker such that firewalling etc forces all network out via the tor docker. This would run laps around the current tor browser bundle for web usability because you'd let rich content through.Tartuffery
Is the trouble for you with X11 security? Or is it that you want this working with windows? Or that you want this to work remotely? All of the above? I think that making this work with vnc is quite possible(though I wouldn't make it the default method because it adds a dependency on vnc). Making subuser work remotely isn't really possible/meaningfull. There is also this: github.com/rogaha/docker-desktop but from the bug reports it seems xpra might be unusable in real life.Recapitulation
L
28

OSX

Jürgen Weigert has the best answer that worked for me on Ubuntu, however on OSX, docker runs inside of VirtualBox and so the solution doesn't work without some more work.

I've got it working with these additional ingredients:

  1. Xquartz (OSX no longer ships with X11 server)
  2. socket forwarding with socat (brew install socat)
  3. bash script to launch the container

I'd appreciate user comments to improve this answer for OSX, I'm not sure if socket forwarding for X is secure, but my intended use is for running the docker container locally only.

Also, the script is a bit fragile in that it's not easy to get the IP address of the machine since it's on our local wireless so it's always some random IP.

The BASH script I use to launch the container:

#!/usr/bin/env bash

CONTAINER=py3:2016-03-23-rc3
COMMAND=/bin/bash
NIC=en0

# Grab the ip address of this box
IPADDR=$(ifconfig $NIC | grep "inet " | awk '{print $2}')

DISP_NUM=$(jot -r 1 100 200)  # random display number between 100 and 200

PORT_NUM=$((6000 + DISP_NUM)) # so multiple instances of the container won't interfer with eachother

socat TCP-LISTEN:${PORT_NUM},reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\" 2>&1 > /dev/null &

XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth.$USER.$$
touch $XAUTH
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -

docker run \
    -it \
    --rm \
    --user=$USER \
    --workdir="/Users/$USER" \
    -v "/Users/$USER:/home/$USER:rw" \
    -v $XSOCK:$XSOCK:rw \
    -v $XAUTH:$XAUTH:rw \
    -e DISPLAY=$IPADDR:$DISP_NUM \
    -e XAUTHORITY=$XAUTH \
    $CONTAINER \
    $COMMAND

rm -f $XAUTH
kill %1       # kill the socat job launched above

I'm able to get xeyes and matplotlib working with this approach.

Windows 7+

It's a bit easier on Windows 7+ with MobaXterm:

  1. Install MobaXterm for windows
  2. Start MobaXterm
  3. Configure X server: Settings -> X11 (tab) -> set X11 Remote Access to full
  4. Use this BASH script to launch the container

run_docker.bash:

#!/usr/bin/env bash

CONTAINER=py3:2016-03-23-rc3
COMMAND=/bin/bash
DISPLAY="$(hostname):0"
USER=$(whoami)

docker run \
    -it \
    --rm \
    --user=$USER \
    --workdir="/home/$USER" \
    -v "/c/Users/$USER:/home/$USER:rw" \
    -e DISPLAY \
    $CONTAINER \
    $COMMAND

xeyes running on PC

Laevorotation answered 23/3, 2016 at 23:12 Comment(6)
i didnt understand what you meant by the bash script - how do i run it in windows?Laxity
@Laxity I do software development on windows using GIT, so I have the GIT-bash shell available to me.Laevorotation
I followed the steps. However, I get error: XDG_RUNTIME_DIR not set in the environment. and Error: cannot open display: VAIO:0.0. Did you encounter something like this?Norling
I get an error pertaining to the user not being found i.e. "no matching entries in passwd file" Any leads?Gunnar
@Laevorotation what kind of modifications were required in the dockerfile? Like mentioned in the previous comment I am getting the same error "unable to find user <username>: no matching entries in passwd file."Magnificent
@ai2ys, I think I must have added users to the container.Laevorotation
W
26

Sharing host display :0, as stated in some other answers, has two drawbacks:

  • It breaks container isolation due to some X security leaks. For example, keylogging with xev or xinput is possible, and remote control of host applications with xdotool.
  • Applications can have rendering glitches and bad RAM access errors due to missing shared memory for X extension MIT-SHM. (Can also be fixed with isolation degrading option --ipc=host).

Below an example script to run a docker image in Xephyr that addresses this issues.

  • It avoids X security leaks as the docker applications run in a nested X server.
  • MIT-SHM is disabled to avoid RAM access failures.
  • Container security is improved with --cap-drop ALL --security-opt no-new-privileges. Also the container user is not root.
  • An X cookie is created to restrict access to Xephyr display.

The script expects some arguments, first a host window manager to run in Xephyr, second a docker image, optionally third an image command to be executed. To run a desktop environment in docker, use ":" instead of a host window manager.

Closing Xephyr window terminates docker container applications. Terminating the dockered applications closes Xephyr window.

Examples:

  • xephyrdocker "openbox --sm-disable" x11docker/lxde pcmanfm
  • xephyrdocker : x11docker/lxde
  • xephyrdocker xfwm4 --device /dev/snd jess/nes /games/zelda.rom

xephyrdocker script:

#! /bin/bash
#
# Xephyrdocker:     Example script to run docker GUI applications in Xephyr.
#
# Usage:
#   Xephyrdocker WINDOWMANAGER DOCKERIMAGE [IMAGECOMMAND [ARGS]]
#
# WINDOWMANAGER     host window manager for use with single GUI applications.
#                   To run without window manager from host, use ":"
# DOCKERIMAGE       docker image containing GUI applications or a desktop
# IMAGECOMMAND      command to run in image
#
Windowmanager="$1" && shift
Dockerimage="$*"

# Container user
Useruid=$(id -u)
Usergid=$(id -g)
Username="$(id -un)"
[ "$Useruid" = "0" ] && Useruid=1000 && Usergid=1000 && Username="user$Useruid"

# Find free display number
for ((Newdisplaynumber=1 ; Newdisplaynumber <= 100 ; Newdisplaynumber++)) ; do
  [ -e /tmp/.X11-unix/X$Newdisplaynumber ] || break
done
Newxsocket=/tmp/.X11-unix/X$Newdisplaynumber

# cache folder and files
Cachefolder=/tmp/Xephyrdocker_X$Newdisplaynumber
[ -e "$Cachefolder" ] && rm -R "$Cachefolder"
mkdir -p $Cachefolder
Xclientcookie=$Cachefolder/Xcookie.client
Xservercookie=$Cachefolder/Xcookie.server
Xinitrc=$Cachefolder/xinitrc
Etcpasswd=$Cachefolder/passwd

# command to run docker
# --rm                               created container will be discarded.
# -e DISPLAY=$Newdisplay             set environment variable to new display
# -e XAUTHORITY=/Xcookie             set environment variable XAUTHORITY to provided cookie
# -v $Xclientcookie:/Xcookie:ro      provide cookie file to container
# -v $NewXsocket:$NewXsocket:ro      Share new X socket of Xephyr
# --user $Useruid:$Usergid           Security: avoid root in container
# -v $Etcpasswd:/etc/passwd:ro       /etc/passwd file with user entry
# --group-add audio                  Allow access to /dev/snd if shared with '--device /dev/snd' 
# --cap-drop ALL                     Security: disable needless capabilities
# --security-opt no-new-privileges   Security: forbid new privileges
Dockercommand="docker run --rm \
  -e DISPLAY=:$Newdisplaynumber \
  -e XAUTHORITY=/Xcookie \
  -v $Xclientcookie:/Xcookie:ro \
  -v $Newxsocket:$Newxsocket:rw \
  --user $Useruid:$Usergid \
  -v $Etcpasswd:/etc/passwd:ro \
  --group-add audio \
  --env HOME=/tmp \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  $(command -v docker-init >/dev/null && echo --init) \
  $Dockerimage"

echo "docker command: 
$Dockercommand
"

# command to run Xorg or Xephyr
# /usr/bin/Xephyr                an absolute path to X server executable must be given for xinit
# :$Newdisplaynumber             first argument has to be new display
# -auth $Xservercookie           path to cookie file for X server. Must be different from cookie file of client, not sure why
# -extension MIT-SHM             disable MIT-SHM to avoid rendering glitches and bad RAM access (+ instead of - enables it)
# -nolisten tcp                  disable tcp connections for security reasons
# -retro                         nice retro look
Xcommand="/usr/bin/Xephyr :$Newdisplaynumber \
  -auth $Xservercookie \
  -extension MIT-SHM \
  -nolisten tcp \
  -screen 1000x750x24 \
  -retro"

echo "X server command:
$Xcommand
"

# create /etc/passwd with unprivileged user
echo "root:x:0:0:root:/root:/bin/sh" >$Etcpasswd
echo "$Username:x:$Useruid:$Usergid:$Username,,,:/tmp:/bin/sh" >> $Etcpasswd

# create xinitrc
{ echo "#! /bin/bash"

  echo "# set environment variables to new display and new cookie"
  echo "export DISPLAY=:$Newdisplaynumber"
  echo "export XAUTHORITY=$Xclientcookie"

  echo "# same keyboard layout as on host"
  echo "echo '$(setxkbmap -display $DISPLAY -print)' | xkbcomp - :$Newdisplaynumber"

  echo "# create new XAUTHORITY cookie file" 
  echo ":> $Xclientcookie"
  echo "xauth add :$Newdisplaynumber . $(mcookie)"
  echo "# create prepared cookie with localhost identification disabled by ffff,"
  echo "# needed if X socket is shared instead connecting over tcp. ffff means 'familiy wild'"
  echo 'Cookie=$(xauth nlist '":$Newdisplaynumber | sed -e 's/^..../ffff/')" 
  echo 'echo $Cookie | xauth -f '$Xclientcookie' nmerge -'
  echo "cp $Xclientcookie $Xservercookie"
  echo "chmod 644 $Xclientcookie"

  echo "# run window manager in Xephyr"
  echo $Windowmanager' & Windowmanagerpid=$!'

  echo "# show docker log"
  echo 'tail --retry -n +1 -F '$Dockerlogfile' 2>/dev/null & Tailpid=$!'

  echo "# run docker"
  echo "$Dockercommand"
} > $Xinitrc

xinit  $Xinitrc -- $Xcommand
rm -Rf $Cachefolder

This script is maintained at x11docker wiki. A more advanced script is x11docker that also supports features like GPU acceleration, webcam and printer sharing and so on.

Woodbridge answered 24/9, 2016 at 21:13 Comment(0)
L
19

Here's a lightweight solution that avoids having to install any X server, vnc server or sshd daemon on the container. What it gains in simplicity it loses in security and isolation.

It assumes that you connect to the host machine using ssh with X11 forwarding.

In the sshd configuration of the host, add the line

X11UseLocalhost no

So that the forwarded X server port on the host is opened on all interfaces (not just lo) and in particular on the Docker virtual interface, docker0.

The container, when run, needs access to the .Xauthority file so that it can connect to the server. In order to do that, we define a read-only volume pointing to the home directory on the host (maybe not a wise idea!) and also set the XAUTHORITY variable accordingly.

docker run -v $HOME:/hosthome:ro -e XAUTHORITY=/hosthome/.Xauthority

That is not enough, we also have to pass the DISPLAY variable from the host, but substituting the hostname by the ip:

-e DISPLAY=$(echo $DISPLAY | sed "s/^.*:/$(hostname -i):/")

We can define an alias:

 alias dockerX11run='docker run -v $HOME:/hosthome:ro -e XAUTHORITY=/hosthome/.Xauthority -e DISPLAY=$(echo $DISPLAY | sed "s/^.*:/$(hostname -i):/")'

And test it like this:

dockerX11run centos xeyes
Landside answered 30/6, 2014 at 19:51 Comment(4)
(This is great for trusted apps. For any kind of sandboxing, though, you want to avoid X-forwarding.)Tartuffery
If you'd rather not mount the whole home directory into the container you can just mount the .Xauthority file itself: -v $HOME/.Xauthority:/root/.Xauthority -e XAUTHORITY=/root/.Xauthority.Catastrophe
Instead of changing X11UseLocalhost, you can also use the additional option --net=host for the docker run command (found here).Bunchy
--net=host is a bad idea as now if you open a port in the container it will be open in the host as well...Westbound
E
17

While the answer by Jürgen Weigert essentially covers this solution, it wasn't clear to me at first what was being described there. So I'll add my take on it, in case anyone else needs clarification.

First off, the relevant documentation is the X security manpage.

Numerous online sources suggest just mounting the X11 unix socket and the ~/.Xauthority file into the container. These solutions often work by luck, without really understanding why, e.g. the container user ends up with the same UID as the user, so there's no need for magic key authorization.

First off, the Xauthority file has mode 0600, so the container user won't be able to read it unless it has the same UID.

Even if you copy the file into the container, and change the ownership, there's still another problem. If you run xauth list on the host and container, with the same Xauthority file, you'll see different entries listed. This is because xauth filters the entries depending on where it's run.

The X client in the container (i.e. GUI app) will behave the same as xauth. In other words, it doesn't see the magic cookie for the X session running on the user's desktop. Instead, it sees the entries for all the "remote" X sessions you've opened previously (explained below).

So, what you need to do is add a new entry with the hostname of the container and the same hex key as the host cookie (i.e. the X session running on your desktop), e.g.:

containerhostname/unix:0   MIT-MAGIC-COOKIE-1   <shared hex key>

The catch is that the cookie has to be added with xauth add inside the container:

touch ~/.Xauthority
xauth add containerhostname/unix:0 . <shared hex key>

Otherwise, xauth tags it in a way that it's only seen outside the container.

The format for this command is:

xauth add hostname/$DISPLAY protocol hexkey

Where . represents the MIT-MAGIC-COOKIE-1 protocol.

Note: There's no need to copy or bind-mount .Xauthority into the container. Just create a blank file, as shown, and add the cookie.

Jürgen Weigert's answer gets around this by using the FamilyWild connection type to create a new authority file on the host and copy it into the container. Note that it first extracts the hex key for the current X session from ~/.Xauthority using xauth nlist.

So the essential steps are:

  • Extract the hex key of the cookie for the user's current X session.
  • Create a new Xauthority file in the container, with the container hostname and the shared hex key (or create a cookie with the FamilyWild connection type).

I admit that I don't understand very well how FamilyWild works, or how xauth or X clients filter entries from the Xauthority file depending where they're run. Additional information on this is welcome.

If you want to distribute your Docker app, you'll need a start script for running the container that gets the hex key for the user's X session, and imports it into the container in one of the two ways explained previously.

It also helps to understand the mechanics of the authorization process:

  • An X client (i.e. GUI application) running in the container looks in the Xauthority file for a cookie entry that matches the container's hostname and the value of $DISPLAY.
  • If a matching entry is found, the X client passes it with its authorization request to the X server, through the appropriate socket in the /tmp/.X11-unix directory mounted in the container.

Note: The X11 Unix socket still needs to be mounted in the container, or the container will have no route to the X server. Most distributions disable TCP access to the X server by default for security reasons.

For additional information, and to better grasp how the X client/server relationship works, it's also helpful to look at the example case of SSH X forwarding:

  • The SSH server running on a remote machine emulates its own X server.
  • It sets the value of $DISPLAY in the SSH session to point to its own X server.
  • It uses xauth to create a new cookie for the remote host, and adds it to the Xauthority files for both the local and remote users.
  • When GUI apps are started, they talk to SSH's emulated X server.
  • The SSH server forwards this data back to the SSH client on your local desktop.
  • The local SSH client sends the data to the X server session running on your desktop, as if the SSH client was actually an X client (i.e. GUI app).
  • The X server uses the received data to render the GUI on your desktop.
  • At the start of this exchange, the remote X client also sends an authorization request, using the cookie that was just created. The local X server compares it with its local copy.
Elijah answered 11/8, 2018 at 1:31 Comment(1)
"If you run xauth list on the host and container, with the same Xauthority file, you'll see different entries listed. " -> if you REALLY see this, then this is a bug. "The SSH server running on a remote machine emulates its own X server." -> no, it does not. It only opens a TCP port on the remote end and forwards the traffic to the local end, where an X server is needed to process it.Ardisardisj
F
13

This is not lightweight but is a nice solution that gives docker feature parity with full desktop virtualization. Both Xfce4 or IceWM for Ubuntu and CentOS work, and the noVNC option makes for an easy access through a browser.

https://github.com/ConSol/docker-headless-vnc-container

It runs noVNC as well as tigerVNC's vncserver. Then it calls startx for given Window Manager. In addition, libnss_wrapper.so is used to emulate password management for the users.

Fustian answered 29/3, 2017 at 1:0 Comment(6)
has anyone tested this?Chile
@Chile yes, and works fine. Since then I also tried xpra in docker, which is root-less X. xpra was the best suited IMO and is more efficient than VNC.Fustian
Just to be clear... Can I have a full desktop experience (GNOME, KDE) with this image?Chile
I only tried the Xfce4 and IceWM (which is in that repo). Of course the experience will be limited, for example mounting devices will not show up in the desktop (gvfs) unless you pass --device /dev/... to the docker and set necessary --cap privileges. That defeats the purpose of containment, but you can pass through devices. With some tweaking it should be possible I believe to run GNOME/KDE under VNC. I ran multiple X in docker with nvidia cards (no VNC or Xpra), so that is certainly doable.Fustian
We didn't tried it so far. The biggest challenge on this would be to bring up a working D-Bus daemon. Most of the gnome or KDE desktops will need them. May the ubuntu-desktop-lxde-vnc project helps you there.Singlecross
works perfect with noVNC client directly in browser. Tested: consol/ubuntu-xfce-vncGilliangilliard
S
13

The solution given at http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/ does seem to be an easy way of starting GUI applications from inside the containers ( I tried for firefox over ubuntu 14.04) but I found that a small additional change is required to the solution posted by the author.

Specifically, for running the container, the author has mentioned:

    docker run -ti --rm \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    firefox

But I found that (based on a particular comment on the same site) that two additional options

    -v $HOME/.Xauthority:$HOME/.Xauthority

and

    -net=host 

need to be specified while running the container for firefox to work properly:

    docker run -ti --rm \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v $HOME/.Xauthority:$HOME/.Xauthority \
    -net=host \
    firefox

I have created a docker image with the information on that page and these additional findings: https://hub.docker.com/r/amanral/ubuntu-firefox/

Stringer answered 13/9, 2017 at 14:53 Comment(3)
I have found that you don't even eed to pass the /tmp/.X11-unix socket at all. It just works with mounting .Xauthority and --net=host.Perplexity
This is actually the only solution that works these days. Using /tmp/.X11-unix as volume no longer works, as docker silently refuses volume mounts from sticky directories.Hesperidin
I think it depends on what distro you're using. You definitely can bind-mount the X11 Unix socket on CentOS. It's also important to understand what --network=host does. It gives your container full access to the host's network stack, which may be undesirable, depending on what you're trying to do. If you're just tinkering with running containerized GUIs on your desktop, then it shouldn't matter.Elijah
P
9

The other solutions should work, but here is a solution for docker-compose.

To fix that error, you need to pass $DISPLAY and .X11-unix to docker, as well as grant the user who started docker access to xhost.

Within docker-compose.yml file:

version: '2'
services:
    node:
        build: .
        container_name: node
        environment:
            - DISPLAY
        volumes:
            - /tmp/.X11-unix:/tmp/.X11-unix

In terminal or script:

  • xhost +si:localuser:$USER
  • xhost +local:docker
  • export DISPLAY=$DISPLAY
  • docker-compose up
Peck answered 15/5, 2018 at 17:51 Comment(0)
D
7

If you want to run a GUI application headless, then read here. What you have to do is to create a virtual monitor with xvfb or other similar software. This is very helpful if you want to run Selenium tests for example with browsers.

Something not mentioned anywhere is that some software actually themselves use sand-boxing with Linux containers. So for example Chrome will never run normally if you don't use the appropriate flag --privileged when running the container.

Dukey answered 18/10, 2014 at 9:23 Comment(0)
M
6

There is another solution by lord.garbage to run GUI apps in a container without using VNC, SSH and X11 forwarding. It is mentioned here too.

Moriarty answered 23/9, 2014 at 13:59 Comment(1)
This is great if security isn't a concern. If the purposes of dockering something are to isolate it, its best to avoid X11 in-out of the container.Tartuffery
W
6

I'm late to the party, but for Mac users who don't want to go down the XQuartz path, here is a working example that builds a Fedora Image, with a Desktop Environment (xfce) using Xvfb and VNC. It's simple, and works:

On a Mac, you can just access it using the Screen Sharing (default) application, connecting to localhost:5901.

Dockerfile:

FROM fedora

USER root

# Set root password, so I know it for the future
RUN echo "root:password123" | chpasswd

# Install Java, Open SSL, etc.
RUN dnf update -y --setopt=deltarpm=false  \
 && dnf install -y --setopt=deltarpm=false \
                openssl.x86_64             \
                java-1.8.0-openjdk.x86_64  \
                xorg-x11-server-Xvfb       \
                x11vnc                     \
                firefox                    \
                @xfce-desktop-environment  \
 && dnf clean all

# Create developer user (password: password123, uid: 11111)
RUN useradd -u 11111 -g users -d /home/developer -s /bin/bash -p $(echo password123 | openssl passwd -1 -stdin) developer

# Copy startup script over to the developer home
COPY start-vnc.sh /home/developer/start-vnc.sh
RUN chmod 700 /home/developer/start-vnc.sh
RUN chown developer.users /home/developer/start-vnc.sh

# Expose VNC, SSH
EXPOSE 5901 22

# Set up VNC Password and DisplayEnvVar to point to Display1Screen0
USER developer
ENV  DISPLAY :1.0
RUN  mkdir ~/.x11vnc
RUN  x11vnc -storepasswd letmein ~/.x11vnc/passwd

WORKDIR /home/developer
CMD ["/home/developer/start-vnc.sh"]

start-vnc.sh

#!/bin/sh

Xvfb :1 -screen 0 1024x768x24 &
sleep 5
x11vnc -noxdamage -many -display :1 -rfbport 5901 -rfbauth ~/.x11vnc/passwd -bg
sleep 2
xfce4-session &

bash
# while true; do sleep 1000; done

Check the linked readme for build and run commands if you want/need.

Winegrower answered 11/5, 2017 at 14:33 Comment(0)
M
5

Based on Jürgen Weigert's answer, I have some improvement:

docker build -t xeyes - << __EOF__
FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes
__EOF__
XSOCK=/tmp/.X11-unix
XAUTH_DIR=/tmp/.docker.xauth
XAUTH=$XAUTH_DIR/.xauth
mkdir -p $XAUTH_DIR && touch $XAUTH
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
docker run -ti -v $XSOCK:$XSOCK -v $XAUTH_DIR:$XAUTH_DIR -e XAUTHORITY=$XAUTH xeyes

The only difference is that it creates a directory $XAUTH_DIR which is used to place $XAUTH file and mount $XAUTH_DIR directory instead of $XAUTH file into docker container.

The benefit of this method is that you can write a command in /etc/rc.local which is to create a empty folder named $XAUTH_DIR in /tmp and change its mode to 777.

tr '\n' '\000' < /etc/rc.local | sudo tee /etc/rc.local >/dev/null
sudo sed -i 's|\x00XAUTH_DIR=.*\x00\x00|\x00|' /etc/rc.local >/dev/null
tr '\000' '\n' < /etc/rc.local | sudo tee /etc/rc.local >/dev/null
sudo sed -i 's|^exit 0.*$|XAUTH_DIR=/tmp/.docker.xauth; rm -rf $XAUTH_DIR; install -m 777 -d $XAUTH_DIR\n\nexit 0|' /etc/rc.local

When system restart, before user login, docker will mount the $XAUTH_DIR directory automatically if container's restart policy is "always". After user login, you can write a command in ~/.profile which is to create $XAUTH file, then the container will automatically use this $XAUTH file.

tr '\n' '\000' < ~/.profile | sudo tee ~/.profile >/dev/null
sed -i 's|\x00XAUTH_DIR=.*-\x00|\x00|' ~/.profile
tr '\000' '\n' < ~/.profile | sudo tee ~/.profile >/dev/null
echo "XAUTH_DIR=/tmp/.docker.xauth; XAUTH=\$XAUTH_DIR/.xauth; touch \$XAUTH; xauth nlist \$DISPLAY | sed -e 's/^..../ffff/' | xauth -f \$XAUTH nmerge -" >> ~/.profile

Afterall, the container will automatically get the Xauthority file every time the system restart and user login.

Medford answered 11/11, 2017 at 12:39 Comment(0)
P
5

I managed to run a video stream from an USB camera using opencv in docker by following these steps:

  1. Let docker access the X server

    xhost +local:docker
    
  2. Create the X11 Unix socket and the X authentication file

    XSOCK=/tmp/.X11-unix
    XAUTH=/tmp/.docker.xauth
    
  3. Add proper permissions

    xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
    
  4. Set the Qt rendering speed to "native", so it doesn't bypass the X11 rendering engine

    export QT_GRAPHICSSYSTEM=native
    
  5. Tell Qt to not use MIT-SHM (shared memory) - that way it should be also safer security-wise

    export QT_X11_NO_MITSHM=1
    
  6. Update the docker run command

    docker run -it \
               -e DISPLAY=$DISPLAY \
               -e XAUTHORITY=$XAUTH \
               -v $XSOCK:$XSOCK \
               -v $XAUTH:$XAUTH \
               --runtime=nvidia \
               --device=/dev/video0:/dev/video0 \
               nvcr.io/nvidia/pytorch:19.10-py3
    

Note: When you finish the the project, return the access controls at their default value - xhost -local:docker

More details: Using GUI's with Docker

Credit: Real-time and video processing object detection using Tensorflow, OpenCV and Docker

Pheni answered 31/10, 2019 at 15:58 Comment(1)
"Create the X11 Unix socket and the X authentication file" doiesn't create any files, it just defines variables?Westbound
A
4

For OpenGL rendering with the Nvidia driver, use the following image:

https://github.com/thewtex/docker-opengl-nvidia

For other OpenGL implementations, make sure the image has the same implementation as the host.

Atypical answered 21/8, 2014 at 4:29 Comment(0)
S
4

You can allow the Docker user (here: root) to access the X11 display:

XSOCK=/tmp/.X11-unix
xhost +SI:localuser:root 
docker run -t -i --rm -v $XSOCK:$XSOCK:ro -e DISPLAY=unix$(DISPLAY) image 
xhost -SI:localuser:root
Scrotum answered 11/8, 2016 at 10:0 Comment(0)
D
3

OSX (10.13.6, high sierra)

Similar to @Nick's answer, but his solution did not work for me.

First install socat by doing brew install socat, and install XQuartz (https://www.xquartz.org/)

Then followed these steps here (http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/) in the comments section:

1. in one mac terminal i started:

socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\"

2. and in another mac terminal I ran:

docker run -ti --rm \
-e DISPLAY=$(ipconfig getifaddr en0):0 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
firefox

I was also able to launch CLion from my debian docker container too.

Dawkins answered 27/7, 2018 at 23:44 Comment(0)
S
3

Yet another answer in case you already built the image:

  1. invoke docker w/o sudo (How to fix docker: Got permission denied issue)

  2. share the same USER & home & passwd between host and container share (tips: use user id instead of user name)

  3. the dev folder for driver dependent libs to work well

  4. plus X11 forward.

    docker run --name=CONTAINER_NAME --network=host --privileged \
      -v /dev:/dev \
      -v `echo ~`:/home/${USER} \
      -p 8080:80 \
      --user=`id -u ${USER}` \
      --env="DISPLAY" \
      --volume="/etc/group:/etc/group:ro" \
      --volume="/etc/passwd:/etc/passwd:ro" \
      --volume="/etc/shadow:/etc/shadow:ro" \
      --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \
      --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
      -it REPO:TAG /bin/bash

you may ask, whats the point to use docker if so many things are the same? well, one reason I can think of is to overcome the package depency hell (https://en.wikipedia.org/wiki/Dependency_hell).

So this type of usage is more suitable for developer I think.

Sartor answered 28/3, 2019 at 2:36 Comment(1)
This is the only one that would work for me. For my purposes, I was able to minimize it to this: docker run --network=host --volume=echo ~:/home/${USER} --user=id -u ${USER} --env="DISPLAY" --volume="/etc/passwd:/etc/passwd:ro" -it REPO:TAG /bin/bashEntablature
A
2

fcwu/docker-ubuntu-vnc-desktop (Ubuntu 18.04, 20.04)

https://github.com/fcwu/docker-ubuntu-vnc-desktop provides a convenient setup. That setup is not minimized. It would be good to minimize it. But I just don't have the time, and that one just works every time I try, so I tend to just use it. On the upside, because it is not minimized, it tends to test more complex programs you might actually to see that they are actually working through the infinitely many pitfalls of Docker. Also, since setups breaks on every guest/host update, a minimization would arguably only work for a limited period until you'd have to reminimize that project again.

To fire it up just run:

sudo docker run --name ubvnc -p 6080:80 -p 5900:5900 dorowu/ubuntu-desktop-lxde-vnc:focal

Then on host either:

enter image description here

To quit just kill docker on the terminal. And to restart the machine:

sudo docker start ubvnc

and then reconnect with VNC. Then to quit the machine:

sudo docker stop ubvnc

You have to wait a few seconds for the VNC server on the guest to start before you can connect.

Chromium inside the guest won't start from the menu. If you try to launch it from the command line it explains why:

Running as root without --no-sandbox is not supported. See https://crbug.com/638180.

so just run it from the CLI with:

chromium-browser --no-sandbox

Firefox does not care however.

TODO: no audio. --device /dev/snd did not help:

EDIT: they added a section for it: https://github.com/fcwu/docker-ubuntu-vnc-desktop/tree/e4922ce92f945fc482994b7a0fd95ca5de7295b3#sound-preview-version-and-linux-only

See also:

Tested on:

  • Ubuntu 19.04 host, fcwu/docker-ubuntu-vnc-desktop, dorowu/ubuntu-desktop-lxde-vnc image id: 70516b87e92d.
  • Ubuntu 21.10 host, dorowu/ubuntu-desktop-lxde-vnc:focal (Ubuntu 20.04)
Akan answered 25/2, 2022 at 11:17 Comment(0)
I
1

Docker with BRIDGE network. for Ubuntu 16.04 with display manager lightdm:

cd /etc/lightdm/lightdm.conf.d
sudo nano user.conf

[Seat:*]
xserver-allow-tcp=true
xserver-command=X -listen tcp

you can use more private permissions

xhost +

docker run --volume="$HOME/.Xauthority:/root/.Xauthority:rw" --env="DISPLAY=$HOST_IP_IN_BRIDGE_NETWORK:0" --net=bridge $container_name
Ison answered 20/11, 2018 at 11:16 Comment(1)
I don't think xhost + is more private, I think that actually opens up the server to any connection per the second highest voted answer. stackoverflow.com/a/25280523Alvar
C
0

There're many good answers here on how to connect GUI app in docker container to X server running on host machine, or how to run virtual X server and connect to container using VNC to access it.

There's however another solution (which is quite useful for say kiosks or home theatres) - you can run X server inside docker container with video output to the monitor connected to your host machine.

First let's create a docker volume to store the X11 socket:

docker volume create --name xsocket

Now we can create an image with X Server:

FROM ubuntu

RUN apt-get update && \
    DEBIAN_FRONTEND='noninteractive' apt-get install -y xorg

CMD /usr/bin/X :0 -nolisten tcp vt1

Let us build it and start it and store the X11 socket in xsocket docker volume:

docker build . -t docker-x-server:latest
docker run --privileged -v xsocket:/tmp/.X11-unix -d docker-x-server:latest

Now we can run a GUI application in another docker container (yay!) and point it to our X server using xsocket volume:

docker run --rm -it -e DISPLAY=:0 -v xsocket:/tmp/.X11-unix:ro stefanscherer/xeyes

If you need input (like keyboard) install xserver-xorg-input-evdev package and add -v /run/udev/data:/run/udev/data since there's no udev in containers by default.

You can even get rid of --privileged flag by granting SYS_TTY_CONFIG capability and binding some devices into container:

docker run --name docker-x-server --device=/dev/input --device=/dev/console --device=/dev/dri --device=/dev/fb0 --device=/dev/tty --device=/dev/tty1 --device=/dev/vga_arbiter --device=/dev/snd  --device=/dev/psaux --cap-add=SYS_TTY_CONFIG  -v xsocket:/tmp/.X11-unix  -d  docker-x-server:latest
Comedietta answered 8/9, 2021 at 23:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.