如何在Linux Docker容器中运行GUI应用程序?
是否有任何镜像可以设置vncserver
或其他内容,以便您可以在例如Firefox周围添加额外的保护层?
# 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'
1234
:docker run -p 5900:5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create
docker run -p 5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create
docker run -p 5900 creack/firefox-vnc x11vnc -forever -usepw -create
docker inspect <容器ID>
或简单地运行 docker ps
),然后使用刚刚找到的端口连接到您主机的IP地址。 - creackdocker 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
-v $XSOCK:$XSOCK -v $XAUTH:$XAUTH
可以简写为 -v $XSOCK -v $XAUTH
。 - Piotr Aleksander Chmielowski$DISPLAY
替换:0
。这意味着xauth nlist $DISPLAY | ...
和docker run -ti -e DISPLAY=$DISPLAY ...
。通常X显示是:0
,但并不总是(特别是如果你通过ssh -X进行连接)。 - johndodo--net = host
。 - mguijarr/tmp/.docker.xauth
文件,并设置为600
权限。这导致容器内的xauth无法读取该文件。可以通过在容器内运行xauth list
来验证。我已经在xauth nlist:0 | ...
命令后添加了chmod 755 $XAUTH
以解决此问题。 - Abai-e DISPLAY=$DISPLAY
),将最后四行替换为: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 xeyes
。 - kgibmFROM 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
构建镜像:
docker build -t firefox .
并运行命令:
docker run -ti --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
firefox
当然,您也可以在运行命令中使用 sh -c"echo script-here"
来执行此操作。
提示:有关音频,请参见:https://dev59.com/el4b5IYBdhLWcg3wiSPV#28985715
$ xhost +
允许来自任何主机的连接到 X。 - Bandoos使用Docker数据卷可以很容易地在容器内部暴露xorg的Unix域套接字。
例如,使用以下Dockerfile:
FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps
ENV DISPLAY :0
CMD xeyes
$ docker build -t xeyes - < Dockerfile
$ XSOCK=/tmp/.X11-unix/X0
$ docker run -v $XSOCK:$XSOCK xeyes
xhost +local
命令即可。不过最好将 ~/.Xauthority
文件放到容器中,以便容器可以进行身份验证。 - Aryeh Leib TaurogCan't open display: :0
。你有什么想法吗? - cboettigxhost +si:localuser:$USER
来授权仅启动容器的用户。 - Nick Breenxhost +local:
缺少尾随冒号。没有冒号,该命令在Ubuntu 16.04上失败了。 - TmTronJürgen Weigert的回答对于Ubuntu系统是最好的解决方案,但是在OSX系统中,Docker运行在VirtualBox中,因此需要进行一些额外的工作才能使其正常运行。
我使用以下附加组件使其正常运行:
我希望用户能够提供评论来改进这个解决方案,在我打算仅在本地运行Docker容器时,我不确定X的套接字转发是否安全。
此外,该脚本有点脆弱,因为很难获取机器的IP地址,因为它在我们的本地无线网络上,所以它总是一些随机IP。
我使用以下BASH脚本来启动容器:
#!/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
在Windows 7+上使用MobaXterm会更容易一些:
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
如其他回答所述,共享主机显示号:0有两个缺点:
xev
或xinput
进行键盘记录是可能的,并且可以使用xdotool
远程控制主机应用程序。--ipc=host
来解决)。以下是一个运行docker镜像在Xephyr中的示例脚本,解决了这些问题。
--cap-drop ALL --security-opt no-new-privileges
提高了容器安全性。此外,容器用户不是root。该脚本需要一些参数,首先是要在Xephyr中运行的主机窗口管理器,第二个是docker镜像,可选地第三个是要执行的镜像命令。 要在docker中运行桌面环境,请使用“:”而不是主机窗口管理器。
关闭Xephyr窗口会终止docker容器应用程序。 终止dockered应用程序将关闭Xephyr窗口。
示例:
xephyrdocker "openbox --sm-disable" x11docker/lxde pcmanfm
xephyrdocker : x11docker/lxde
xephyrdocker xfwm4 --device /dev/snd jess/nes /games/zelda.rom
xephyrdocker脚本:
#! /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
这个脚本维护在x11docker wiki上。 更高级的脚本是x11docker,它还支持GPU加速、网络摄像头和打印机共享等功能。
X11UseLocalhost no
lo
)以及特定的Docker虚拟接口docker0
都开放,需要进行以下操作:.Xauthority
文件才能连接到服务器。为此,我们定义了一个只读卷指向主机上的主目录(也许不是明智的做法!),并相应地设置XAUTHORITY
变量。docker run -v $HOME:/hosthome:ro -e XAUTHORITY=/hosthome/.Xauthority
这还不够,我们还需要从主机传递DISPLAY变量,但是将主机名替换为IP:
-e DISPLAY=$(echo $DISPLAY | sed "s/^.*:/$(hostname -i):/")
我们可以定义一个别名:
alias dockerX11run='docker run -v $HOME:/hosthome:ro -e XAUTHORITY=/hosthome/.Xauthority -e DISPLAY=$(echo $DISPLAY | sed "s/^.*:/$(hostname -i):/")'
然后像这样测试它:
dockerX11run centos xeyes
.Xauthority
文件本身即可:-v $HOME/.Xauthority:/root/.Xauthority -e XAUTHORITY=/root/.Xauthority
。 - Robert HainesX11UseLocalhost
,您也可以在docker run
命令中使用附加选项--net=host
(在此处找到)来解决问题。 - ingomueller.net虽然Jürgen Weigert的回答基本涵盖了这个解决方案,但一开始我并不清楚他在描述什么。因此,我会加入我的观点,以防其他人需要澄清。
首先,相关文档是X安全手册。
许多在线资源建议将X11 Unix套接字和~/.Xauthority
文件挂载到容器中。这些解决方案通常仅仅是运气好,没有真正理解原因,例如容器用户最终具有与用户相同的UID,因此没有必要进行魔法密钥授权。
首先,Xauthority文件具有0600模式,因此容器用户无法读取它,除非它具有相同的UID。
即使您将文件复制到容器中并更改所有权,仍然存在另一个问题。如果您在主机和容器上使用相同的Xauthority
文件运行xauth list
,则会列出不同的条目。这是因为xauth
根据其运行的位置对条目进行过滤。
容器中的X客户端(即GUI应用程序)的行为类似于xauth
。换句话说,它看不到运行在用户桌面上的X会话的魔法cookie。相反,它会看到您之前打开的所有“远程”X会话的条目(下面有解释)。
因此,您需要添加一个新条目,其中包括容器主机名和与主机cookie(即运行在您桌面上的X会话)相同的十六进制密钥,例如:
containerhostname/unix:0 MIT-MAGIC-COOKIE-1 <shared hex key>
问题在于必须在容器内使用xauth add
命令来添加cookie:
touch ~/.Xauthority
xauth add containerhostname/unix:0 . <shared hex key>
否则,xauth
以一种方式标记它,使其仅在容器外部可见。xauth add hostname/$DISPLAY protocol hexkey
其中.
代表MIT-MAGIC-COOKIE-1
协议。
注意:不需要将.Xauthority
文件复制或绑定到容器中。只需按照示例创建一个空文件,并添加cookie即可。
Jürgen Weigert的答案通过使用FamilyWild
连接类型在主机上创建新的权限文件并将其复制到容器中来解决这个问题。请注意,它首先使用xauth nlist
从~/.Xauthority
中提取当前X会话的十六进制密钥。
所以基本步骤如下:
FamilyWild
连接类型创建cookie)。我承认我对FamilyWild
如何工作,或者xauth
或X客户端如何根据它们运行的位置从Xauthority文件中过滤条目没有很好的理解。欢迎提供其他信息。
如果您想分发Docker应用程序,您需要一个启动脚本来运行容器,获取用户X会话的十六进制密钥,并将其导入到容器中通过前面两种方式之一。
还有助于了解授权过程的机制:
$DISPLAY
值匹配的cookie条目。/tmp/.X11-unix
目录中的适当套接字传递给X服务器。注意:仍需要在容器中挂载X11 Unix套接字,否则容器将无法连接到X服务器。大多数发行版出于安全考虑默认禁用对X服务器的TCP访问。
为了进一步了解X客户端/服务器关系的工作原理,也有助于查看SSH X转发的示例案例:
$DISPLAY
值设置为指向自己的X服务器。xauth
为远程主机创建新的cookie,并将其添加到本地和远程用户的Xauthority
文件中。在http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/提供的解决方案似乎是从容器内部启动GUI应用程序的简单方法(我尝试了在Ubuntu 14.04上使用Firefox),但我发现需要对作者发布的解决方案进行一些小的额外更改。
具体来说,对于运行容器,作者已经提到:
docker run -ti --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
firefox
但是我发现(基于同一网站上的一个特定评论)还有两个额外的选项
-v $HOME/.Xauthority:$HOME/.Xauthority
和
-net=host
在运行 Firefox 容器时需要指定以下内容以确保其正常工作:
docker run -ti --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v $HOME/.Xauthority:$HOME/.Xauthority \
-net=host \
firefox
我已经创建了一个包含该页面信息和以下附加发现的Docker镜像:https://hub.docker.com/r/amanral/ubuntu-firefox/
/tmp/.X11-unix
套接字,只需挂载.Xauthority
并使用--net=host
即可正常工作。 - CMCDragonkai--network=host
的作用也很重要。它会给予你的容器完全访问主机网络栈的权限,这可能是不可取的,具体取决于你想做什么。如果你只是在桌面上尝试运行容器化GUI,那么这就无关紧要了。 - orodbhen