Can you run GUI applications in a Docker container

2018-12-31 16:03发布

How can you run GUI applications in a 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?

20条回答
与风俱净
2楼-- · 2018-12-31 16:49

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.

查看更多
梦寄多情
3楼-- · 2018-12-31 16:50

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.
查看更多
琉璃瓶的回忆
4楼-- · 2018-12-31 16:52

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.

查看更多
余欢
5楼-- · 2018-12-31 16:53

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/

查看更多
一个人的天荒地老
6楼-- · 2018-12-31 16:54

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://stackoverflow.com/a/28985715/2835523

查看更多
其实,你不懂
7楼-- · 2018-12-31 16:55

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.

查看更多
登录 后发表回答