你可以在 Linux Docker 容器中运行 GUI 应用程序吗?
Posted
技术标签:
【中文标题】你可以在 Linux Docker 容器中运行 GUI 应用程序吗?【英文标题】:Can you run GUI applications in a Linux Docker container? 【发布时间】:2013-04-24 04:30:48 【问题描述】:如何在 Linux Docker 容器中运行 GUI 应用程序?
是否有任何图像设置 vncserver
或其他东西,以便您可以 - 例如 - 在 Firefox 周围添加一个额外的减速沙箱?
【问题讨论】:
相关:How to make Xvfb display visible? 查看HPC Visualization Containers User Guide 了解一些想法。 【参考方案1】:你可以简单地安装一个 vncserver 和 Firefox :)
我在这里推送了一张图片,vnc/firefox:docker pull creack/firefox-vnc
镜像是用这个 Dockerfile 制作的:
# Firefox over VNC
#
# VERSION 0.1
# DOCKER-VERSION 0.2
FROM ubuntu:12.04
# Make sure the package repository is up to date
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
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'
这将创建一个运行 VNC 的 Docker 容器,密码为 1234
:
对于 Docker 版本 18 或更高版本:
docker run -p 5900:5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create
对于 Docker 1.3 或更高版本:
docker run -p 5900 -e HOME=/ creack/firefox-vnc x11vnc -forever -usepw -create
对于 1.3 版之前的 Docker:
docker run -p 5900 creack/firefox-vnc x11vnc -forever -usepw -create
【讨论】:
如何使用 VNC 客户端远程查看?输入 IP + 端口似乎不起作用。 首先,您需要检查分配的端口(通过执行docker inspect <container id>
或简单地docker ps
,然后使用刚刚找到的端口连接到主机的IP。
creackfirefox-vnc 映像失败并出现错误:输入 VNC 密码:stty:标准输入:设备 fgets 的 ioctl 不合适:没有这样的文件或目录 stty:标准输入:设备 x11vnc -usepw 的 ioctl 不合适:找不到要使用的密码。
用好 docker > 用 Docker 运行 GUI 应用 fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker
没有用户名,密码在答案中明确指出,任何vnc客户端都可以。就我而言,我喜欢原生的 osx。 (从finder中,按command+K并连接到vnc://Xauthority 成为新系统的问题。我可以在运行我的 docker 容器之前放弃使用 xhost + 的任何保护,或者我可以传入一个准备充分的 Xauthority 文件。典型的 Xauthority 文件是特定于主机名的。使用 docker,每个容器都可以有不同的主机名(使用 docker run -h 设置),但即使将容器的主机名设置为与主机系统相同也无济于事。 xeyes(我喜欢这个例子)只会忽略魔术 cookie 并且不将凭据传递给服务器。因此,我们收到一条错误消息“未指定协议无法打开显示”
Xauthority 文件的编写方式可以使主机名无关紧要。 我们需要将 Authentication Family 设置为“FamilyWild”。我不确定,如果 xauth 有一个合适的命令行,那么这里是一个结合 xauth 和 sed 来做到这一点的例子。我们需要更改 nlist 输出的前 16 位。 FamilyWild 的值为 65535 或 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
【讨论】:
请注意,-v $XSOCK:$XSOCK -v $XAUTH:$XAUTH
可以简写为-v $XSOCK -v $XAUTH
@PiotrAleksanderChmielowski 这对我不起作用,Docker 版本 1.12.0,构建 8eab29e
@Dirk:您可能希望将:0
替换为$DISPLAY
。这意味着xauth nlist $DISPLAY | ...
和docker run -ti -e DISPLAY=$DISPLAY ...
。通常 X DISPLAY 是:0
,但并非总是如此(尤其是在您通过 ssh -X 连接时)。
仅供登陆这里的人使用:@PiotrAleksanderChmielowski 评论对我不起作用,我还必须添加--net=host
在 Ubuntu 16.04 上,xauth 创建具有 600
权限的 /tmp/.docker.xauth
文件。这导致 docker 容器内的 xauth 无法读取文件。您可以通过在 docker 容器中运行 xauth list
来验证。我在xauth nlist :0 | ...
命令之后添加了chmod 755 $XAUTH
来解决这个问题。【参考方案3】:
我刚刚找到了这篇博文,想在这里与大家分享,因为我认为这是最好的方法,而且非常简单。
http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/
优点: + docker 容器中没有 x 服务器的东西 + 不需要 vnc 客户端/服务器 + 没有带 x 转发的 ssh + 更小的 docker 容器
缺点: - 在主机上使用 x(不用于安全沙盒)
万一链接有一天会失败,我把最重要的部分放在这里: 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
构建镜像:
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://***.com/a/28985715/2835523
【讨论】:
如何在 Windows 7 上执行此操作?我需要安装 X 服务器吗? 作为这里的大多数答案,我认为这仅适用于 unix - 直到 windows 支持 X 服务器窗口系统。 如果我在 Windows 上安装 X 服务器,甚至将 X 服务器捆绑到我的 Docker 容器中,你认为它可以工作吗? 我想你还需要在Dockerfile中安装apt-get -y install sudo
来创建/etc/sudoers.d
文件夹。
可能还需要允许使用$ xhost +
的任何主机连接到X【参考方案4】:
使用 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
这当然和X-forwarding本质上是一样的。它授予容器对主机上 xserver 的完全访问权限,因此仅在您信任其中的内容时才建议使用它。
注意:如果您担心安全性,更好的解决方案是使用mandatory- 或role-based- 访问控制限制应用程序。 Docker 实现了很好的隔离,但它的设计目的不同。使用 AppArmor、SELinux 或 GrSecurity,它们旨在解决您的问题。
【讨论】:
您还需要允许使用 xhost 等工具从其他主机访问 X 服务器。要完全打开它,请在主机上使用xhost +
。
@Tully 只有xhost +local
是必需的。但是,最好让容器中的~/.Xauthority
文件可用,这样它就可以验证自己。
你是否设法让它在 Mac 上运行(使用 boot2docker)?
这对我来说在带有 docker 1.5 的 Ubuntu 14.04 笔记本电脑上工作得相当好;但现在我在 Ubuntu 15.04、docker 1.6.2 上失败了,错误为Can't open display: :0
。有什么想法吗?
我使用xhost +si:localuser:$USER
仅授权用户启动容器。【参考方案5】:
OSX
Jürgen Weigert 在 Ubuntu 上为我提供了最适合我的答案,但在 OSX 上,docker 在 VirtualBox 内部运行,因此如果没有更多工作,该解决方案将无法运行。
我已经使用这些额外的成分:
-
Xquartz(OSX 不再附带 X11 服务器)
使用 socat 进行套接字转发(brew install socat)
启动容器的bash脚本
感谢用户 cmets 改进 OSX 的这个答案,我不确定 X 的套接字转发是否安全,但我的预期用途是仅在本地运行 docker 容器。
此外,该脚本有点脆弱,因为它位于我们的本地无线网络上,因此获取机器的 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
我可以让 xeyes 和 matplotlib 使用这种方法。
Windows 7+
在 Windows 7+ 上使用 MobaXterm 会更容易一些:
-
为 windows 安装 MobaXterm
启动 MobaXterm
配置 X 服务器:设置 -> X11(选项卡)-> 将 X11 远程访问 设置为 完整
使用此 BASH 脚本启动容器
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
【讨论】:
我不明白你所说的 bash 脚本是什么意思 - 我如何在 Windows 中运行它? @deller 我使用 GIT 在 Windows 上进行软件开发,所以我可以使用 GIT-bash shell。 我按照步骤操作。但是,我得到error: XDG_RUNTIME_DIR not set in the environment.
和Error: cannot open display: VAIO:0.0
。你遇到过这样的事情吗?
我收到与未找到用户有关的错误,即“passwd 文件中没有匹配的条目”任何线索?
@Nick 需要在 dockerfile 中进行哪些修改?就像前面评论中提到的那样,我收到了同样的错误“无法找到用户 :passwd 文件中没有匹配的条目。”【参考方案6】:
您也可以使用子用户:https://github.com/timthelion/subuser
这允许您在 docker 中打包许多 gui 应用程序。到目前为止,Firefox 和 emacs 已经过测试。使用 firefox,webGL 不起作用。 Chromium 根本不起作用。
编辑:声音有效!
EDIT2:自从我第一次发布此内容以来,子用户取得了很大进步。我现在有一个 subuser.org 的网站,以及一个用于连接到 X11 的新安全模型 via XPRA bridging。
【讨论】:
请注意,子用户仍然是非常新的并且相对未经测试。如果您遇到任何问题,请提交错误报告! 如果可以的话,我会避免使用 X11。您的杀手级应用程序将在 docker 中运行 tor 代理,并在子 docker 中运行带有插件的完整浏览器,以便防火墙等通过 tor docker 强制所有网络退出。这将围绕当前的 Tor 浏览器捆绑运行以提高网络可用性,因为您会让丰富的内容通过。 X11 安全性给您带来了麻烦吗?还是您希望它与 Windows 一起使用?或者你想让它远程工作?上述所有的?我认为使用 vnc 进行这项工作是很有可能的(尽管我不会将其设为默认方法,因为它增加了对 vnc 的依赖)。让子用户远程工作实际上是不可能/有意义的。还有这个:github.com/rogaha/docker-desktop,但从错误报告看来,xpra 在现实生活中可能无法使用。【参考方案7】:共享主机显示:0,如其他一些答案所述,有两个缺点:
由于某些 X 安全漏洞,它会破坏容器隔离。例如,可以使用xev
或xinput
进行键盘记录,并使用xdotool
远程控制主机应用程序。
由于缺少 X 扩展 MIT-SHM 的共享内存,应用程序可能会出现渲染故障和错误的 RAM 访问错误。 (也可以通过隔离降级选项--ipc=host
修复)。
下面是一个示例脚本,用于在 Xephyr 中运行解决此问题的 docker 映像。
它避免了 X 安全漏洞,因为 docker 应用程序在嵌套的 X 服务器中运行。 MIT-SHM 被禁用以避免 RAM 访问失败。--cap-drop ALL --security-opt no-new-privileges
提高了容器安全性。容器用户也是不是 root。
创建了一个 X cookie 来限制对 Xephyr 显示器的访问。
脚本需要一些参数,第一个是在 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 加速、网络摄像头和打印机共享等功能。
【讨论】:
【参考方案8】:这是一个轻量级的解决方案,它避免了在容器上安装任何X
服务器、vnc
服务器或sshd
守护程序。它在简单中获得的东西在安全性和隔离中失去了。
假设您使用ssh
和X11
转发连接到主机。
在主机的sshd
配置中,添加一行
X11UseLocalhost no
这样主机上转发的 X 服务器端口在所有接口(不仅仅是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
【讨论】:
(这对于受信任的应用非常有用。不过,对于任何类型的沙盒,您都希望避免 X-forwarding。) 如果您不想将整个主目录挂载到容器中,您可以挂载.Xauthority
文件本身:-v $HOME/.Xauthority:/root/.Xauthority -e XAUTHORITY=/root/.Xauthority
。
除了更改X11UseLocalhost
,您还可以为docker run
命令使用附加选项--net=host
(找到here)。
--net=host
是个坏主意,因为现在如果您在容器中打开一个端口,它也会在主机中打开......【参考方案9】:
虽然Jürgen Weigert 的回答基本上涵盖了这个解决方案,但起初我并不清楚那里描述的是什么。所以我会添加我的看法,以防其他人需要澄清。
首先,相关文档是X security manpage。
许多在线资源建议只需将 X11 unix 套接字和 ~/.Xauthority
文件安装到容器中。这些解决方案通常靠运气,没有真正理解为什么,例如容器用户最终与用户具有相同的 UID,因此不需要魔法密钥授权。
首先,Xauthority 文件的模式为 0600,因此容器用户将无法读取它,除非它具有相同的 UID。
即使您将文件复制到容器中并更改所有权,仍然存在另一个问题。如果您在主机和容器上运行xauth list
,使用相同的Xauthority
文件,您将看到列出的不同条目。这是因为xauth
根据运行位置过滤条目。
容器中的 X 客户端(即 GUI 应用程序)的行为将与 xauth
相同。换句话说,它看不到在用户桌面上运行的 X 会话的魔法 cookie。相反,它会看到您之前打开的所有“远程”X 会话的条目(如下所述)。
因此,您需要做的是添加一个新条目,其中包含容器的主机名和与主机 cookie 相同的十六进制键(即在您的桌面上运行的 X 会话),例如:
containerhostname/unix:0 MIT-MAGIC-COOKIE-1 <shared hex key>
问题是 cookie 必须在容器内添加 xauth add
:
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 会话的十六进制密钥。
所以基本步骤是:
为用户当前的 X 会话提取 cookie 的十六进制键。 在容器中创建一个新的 Xauthority 文件,使用容器主机名和共享的十六进制密钥(或使用FamilyWild
连接类型创建一个 cookie)。
我承认我不太了解FamilyWild
的工作原理,或者xauth
或X 客户端如何根据运行位置过滤Xauthority 文件中的条目。欢迎提供更多信息。
如果您想分发 Docker 应用程序,您需要一个启动脚本来运行容器,该脚本获取用户 X 会话的十六进制密钥,并以前面介绍的两种方式之一将其导入容器。
这也有助于理解授权过程的机制:
在容器中运行的 X 客户端(即 GUI 应用程序)在 Xauthority 文件中查找与容器的主机名和$DISPLAY
的值匹配的 cookie 条目。
如果找到匹配的条目,X 客户端会将其及其授权请求通过容器中安装的/tmp/.X11-unix
目录中的适当套接字传递给 X 服务器。
注意: X11 Unix 套接字仍然需要挂载到容器中,否则容器将没有到 X 服务器的路由。出于安全原因,大多数发行版默认禁用对 X 服务器的 TCP 访问。
要了解更多信息,并更好地掌握 X 客户端/服务器关系的工作原理,查看 SSH X 转发示例也很有帮助:
在远程计算机上运行的 SSH 服务器模拟自己的 X 服务器。 它将SSH会话中$DISPLAY
的值设置为指向它自己的X服务器。
它使用xauth
为远程主机创建一个新的cookie,并将其添加到本地和远程用户的Xauthority
文件中。
当 GUI 应用启动时,它们会与 SSH 的模拟 X 服务器通信。
SSH 服务器将此数据转发回本地桌面上的 SSH 客户端。
本地 SSH 客户端将数据发送到运行在您桌面上的 X 服务器会话,就好像 SSH 客户端实际上是一个 X 客户端(即 GUI 应用程序)。
X 服务器使用接收到的数据在您的桌面上呈现 GUI。
在此交换开始时,远程 X 客户端还使用刚刚创建的 cookie 发送授权请求。本地 X 服务器将其与其本地副本进行比较。
【讨论】:
"如果您在主机和容器上运行 xauth list,使用相同的 Xauthority 文件,您将看到列出的不同条目。" -> 如果您真的看到这个,那么这是一个错误。 “在远程机器上运行的 SSH 服务器模拟它自己的 X 服务器。” -> 不,它没有。它只在远端打开一个 TCP 端口,并将流量转发到本地,本地需要 X 服务器来处理。【参考方案10】:这不是轻量级的,但它是一个很好的解决方案,它使 docker 功能与完整的桌面虚拟化相当。适用于 Ubuntu 和 CentOS 的 Xfce4 或 IceWM 都可以工作,noVNC
选项可让您通过浏览器轻松访问。
https://github.com/ConSol/docker-headless-vnc-container
它运行noVNC
以及tigerVNC
的vncserver。然后它为给定的窗口管理器调用startx
。此外,libnss_wrapper.so
用于模拟用户的密码管理。
【讨论】:
有人测试过吗? @guilhermecgs 是的,并且工作正常。从那以后,我还在 docker 中尝试了xpra
,它是无根 X。xpra
是最适合 IMO 并且比 VNC 更高效。
澄清一下...我可以使用此图像获得完整的桌面体验(GNOME、KDE)吗?
我只尝试了 Xfce4 和 IceWM(在那个 repo 中)。当然体验会受到限制,例如挂载设备不会显示在桌面 (gvfs) 中,除非您将 --device /dev/...
传递给 docker 并设置必要的 --cap
权限。这违背了收容的目的,但你可以通过设备。通过一些调整,我相信应该可以在 VNC 下运行 GNOME/KDE。我使用 nvidia 卡(没有 VNC 或 Xpra)在 docker 中运行了多个 X,所以这当然是可行的。
到目前为止我们还没有尝试过。最大的挑战是启动一个工作的 D-Bus 守护进程。大多数 gnome 或 KDE 桌面都需要它们。愿ubuntu-desktop-lxde-vnc 项目帮助您。【参考方案11】:
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
。
这实际上是目前唯一有效的解决方案。使用 /tmp/.X11-unix
作为卷不再起作用,因为 docker 默默地拒绝从粘性目录挂载卷。
我认为这取决于您使用的发行版。您绝对可以在 CentOS 上绑定安装 X11 Unix 套接字。了解--network=host
的作用也很重要。它使您的容器可以完全访问主机的网络堆栈,这可能是不可取的,具体取决于您要执行的操作。如果您只是在桌面上运行容器化的 GUI 进行修补,那没关系。【参考方案12】:
其他解决方案应该可以,但这里是docker-compose
的解决方案。
要修复该错误,您需要将 $DISPLAY 和 .X11-unix 传递给 docker,并授予启动 docker 的用户对 xhost 的访问权限。
docker-compose.yml
文件内:
version: '2'
services:
node:
build: .
container_name: node
environment:
- DISPLAY
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix
在终端或脚本中:
xhost +si:localuser:$USER
xhost +local:docker
export DISPLAY=$DISPLAY
docker-compose up
【讨论】:
【参考方案13】:如果您想无头运行 GUI 应用程序,请阅读 here。您要做的就是用xvfb
或其他类似软件创建一个虚拟监视器。如果您想使用浏览器运行 Selenium 测试,这将非常有用。
任何地方都没有提到的是,某些软件实际上本身使用沙盒与 Linux 容器。因此,例如,如果您在运行容器时不使用适当的标志 --privileged
,Chrome 将永远无法正常运行。
【讨论】:
【参考方案14】:another solution by lord.garbage 可以在容器中运行 GUI 应用程序,而无需使用 VNC、SSH 和 X11 转发。也提到了here。
【讨论】:
如果安全不是问题,那就太好了。如果 dockering 的目的是为了隔离它,最好避免 X11 进出容器。【参考方案15】:我迟到了,但是对于不想走 XQuartz 路径的 Mac 用户,这里有一个使用 Xvfb
和桌面环境 (xfce) 构建 Fedora 映像的工作示例VNC
。这很简单,而且很有效:
在 Mac 上,您可以使用 屏幕共享(默认)应用程序访问它,连接到 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"]
启动-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
如果需要/需要,请检查链接的 readme 以获取构建和运行命令。
【讨论】:
【参考方案16】:根据Jürgen Weigert的回答,我有一些改进:
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
唯一的区别是它创建了一个目录 $XAUTH_DIR 用于放置 $XAUTH 文件并将 $XAUTH_DIR 目录而不是 $XAUTH 文件挂载到 docker 容器中。
这种方法的好处是可以在/etc/rc.local中写一条命令,在/tmp中创建一个名为$XAUTH_DIR的空文件夹,并将其模式改为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
系统重启时,如果容器的重启策略为“always”,docker会在用户登录前自动挂载$XAUTH_DIR目录。用户登录后,你可以在~/.profile 中写一条命令来创建$XAUTH 文件,然后容器会自动使用这个$XAUTH 文件。
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
毕竟每次系统重启和用户登录时,容器都会自动获取Xauthority文件。
【讨论】:
【参考方案17】:对于使用 Nvidia 驱动程序的 OpenGL 渲染,请使用以下图像:
https://github.com/thewtex/docker-opengl-nvidia
对于其他 OpenGL 实现,请确保图像与主机具有相同的实现。
【讨论】:
【参考方案18】:按照以下步骤,我设法在docker
中使用opencv
从USB 摄像头运行视频流:
让docker访问X服务器
xhost +local:docker
创建 X11 Unix 套接字和 X 身份验证文件
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
添加适当的权限
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
将Qt渲染速度设置为“native”,这样就不会绕过X11渲染引擎
export QT_GRAPHICSSYSTEM=native
告诉 Qt 不要使用 MIT-SHM(共享内存)——这样在安全方面也应该更安全
export QT_X11_NO_MITSHM=1
更新 docker run 命令
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
注意:完成项目后,将访问控制返回其默认值 - xhost -local:docker
更多详情:Using GUI's with Docker
信用:Real-time and video processing object detection using Tensorflow, OpenCV and Docker
【讨论】:
“创建 X11 Unix 套接字和 X 身份验证文件”不创建任何文件,它只是定义变量?【参考方案19】:您可以允许 Docker 用户(此处为 root)访问 X11 显示:
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
【讨论】:
【参考方案20】:OSX(10.13.6,高山)
与@Nick 的回答类似,但他的解决方案对我不起作用。
首先通过brew install socat
安装socat,然后安装XQuartz (https://www.xquartz.org/)
然后在 cmets 部分执行以下步骤 (http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/):
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
我也可以从我的 debian docker 容器中启动 CLion。
【讨论】:
【参考方案21】:如果您已经构建了图像,还有另一个答案:
无需 sudo 即可调用 docker (How to fix docker: Got permission denied issue)
在主机和容器共享之间共享相同的 USER & home & passwd (提示:使用用户 ID 代替用户名)
驱动依赖库的 dev 文件夹可以正常工作
加 X11 前进。
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
你可能会问,如果这么多东西都是一样的,那么使用 docker 有什么意义呢?嗯,我能想到的一个原因是克服包依赖地狱 (https://en.wikipedia.org/wiki/Dependency_hell)。
所以我认为这种用法更适合开发者。
【讨论】:
这是唯一适合我的。出于我的目的,我能够将其最小化为: docker run --network=host --volume=echo ~
:/home/$USER --user=id -u $USER
--env="DISPLAY" - -volume="/etc/passwd:/etc/passwd:ro" -it REPO:TAG /bin/bash【参考方案22】:
带有 BRIDGE 网络的 Docker。 对于带有显示管理器 lightdm 的 Ubuntu 16.04:
cd /etc/lightdm/lightdm.conf.d
sudo nano user.conf
[Seat:*]
xserver-allow-tcp=true
xserver-command=X -listen tcp
你可以使用更多的私有权限
xhost +
docker run --volume="$HOME/.Xauthority:/root/.Xauthority:rw" --env="DISPLAY=$HOST_IP_IN_BRIDGE_NETWORK:0" --net=bridge $container_name
【讨论】:
我不认为xhost +
更私密,我认为这实际上会根据投票第二高的答案打开服务器到任何连接。 ***.com/a/25280523【参考方案23】:
这里有很多关于如何将 docker 容器中的 GUI 应用程序连接到主机上运行的 X 服务器,或者如何运行虚拟 X 服务器并使用 VNC 连接到容器来访问它的好答案。
还有另一种解决方案(这对于信息亭或家庭影院来说非常有用) - 您可以在 docker 容器内运行 X 服务器,并将视频输出到连接到主机的监视器。
首先让我们创建一个 docker 卷来存储 X11 套接字:
docker volume create --name xsocket
现在我们可以使用 X Server 创建图像了:
FROM ubuntu
RUN apt-get update && \
DEBIAN_FRONTEND='noninteractive' apt-get install -y xorg
CMD /usr/bin/X :0 -nolisten tcp vt1
让我们构建并启动它并将 X11 套接字存储在 xsocket
docker 卷中:
docker build . -t docker-x-server:latest
docker run --privileged -v xsocket:/tmp/.X11-unix -d docker-x-server:latest
现在我们可以在另一个 docker 容器中运行一个 GUI 应用程序(耶!)并使用 xsocket
卷将其指向我们的 X 服务器:
docker run --rm -it -e DISPLAY=:0 -v xsocket:/tmp/.X11-unix:ro stefanscherer/xeyes
如果您需要输入(如键盘)安装xserver-xorg-input-evdev
包并添加-v /run/udev/data:/run/udev/data
,因为默认情况下容器中没有udev。
您甚至可以通过授予SYS_TTY_CONFIG
功能并将一些设备绑定到容器中来摆脱--privileged
标志:
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
【讨论】:
【参考方案24】:fcwu/docker-ubuntu-vnc-desktop
(Ubuntu 18.04、20.04)
https://github.com/fcwu/docker-ubuntu-vnc-desktop 提供了方便的设置。该设置并未最小化。最好将其最小化。但我只是没有时间,而且我每次尝试时都会使用它,所以我倾向于使用它。从好的方面来说,因为它没有被最小化,它往往会测试更复杂的程序,你可能会发现它们实际上正在克服 Docker 的无数陷阱。此外,由于每次来宾/主机更新都会中断设置,因此可以说最小化只能在有限的时间内起作用,直到您不得不再次重新最小化该项目。
要启动它,只需运行:
sudo docker run --name ubvnc -p 6080:80 -p 5900:5900 dorowu/ubuntu-desktop-lxde-vnc:focal
然后在主机上:
访问:http://127.0.0.1:6080/#/,它运行 noVNC 更有限的 javascript VNC 客户端
运行:
sudo apt-get install tigervnc-viewer
xtigervncviewer :5900
要进入全屏模式,请按 F8 并单击菜单条目,或者直接按 F8 后按 T:https://superuser.com/questions/285843/how-do-i-switch-in-out-of-fullscreen-mode-from-the-command-line-in-realvnc 您可能需要关闭并重新打开屏幕才能使图像变大。
我也尝试过vinagre
,但在 YouTube 上滚动 Firefox 时会慢很多。
在vinagre
中,您可能希望go into full screen mode 能够看到完整的桌面
要退出,只需在终端上杀死 docker
。并重新启动机器:
sudo docker start ubvnc
然后重新连接 VNC。然后退出机器:
sudo docker stop ubvnc
您必须等待几秒钟才能启动客户机上的 VNC 服务器,然后才能连接。
来宾内部的 Chromium 不会从菜单启动。如果您尝试从命令行启动它,它会解释原因:
Running as root without --no-sandbox is not supported. See https://crbug.com/638180.
所以只需从 CLI 运行它:
chromium-browser --no-sandbox
Firefox 不在乎。
TODO:没有音频。 --device /dev/snd
没有帮助:
编辑:他们为它添加了一个部分:https://github.com/fcwu/docker-ubuntu-vnc-desktop/tree/e4922ce92f945fc482994b7a0fd95ca5de7295b3#sound-preview-version-and-linux-only
另见:
How to open Ubuntu GUI inside a Docker image测试日期:
Ubuntu 19.04 主机,fcwu/docker-ubuntu-vnc-desktop
,dorowu/ubuntu-desktop-lxde-vnc
图像 ID:70516b87e92d。
Ubuntu 21.10 主机,dorowu/ubuntu-desktop-lxde-vnc:focal
(Ubuntu 20.04)
【讨论】:
以上是关于你可以在 Linux Docker 容器中运行 GUI 应用程序吗?的主要内容,如果未能解决你的问题,请参考以下文章