你能在Linux Docker容器中运行GUI应用程序吗?

505

如何在Linux Docker容器中运行GUI应用程序?

是否有任何镜像可以设置vncserver或其他内容,以便您可以在例如Firefox周围添加额外的保护层?


请查看HPC可视化容器用户指南获取一些想法。 - kenorb
24个回答

289
你可以简单地安装一个带有Firefox的vncserver :)
我在这里推送了一个镜像,vnc/firefox,命令如下:docker pull creack/firefox-vnc
这个镜像是用这个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'

这将创建一个运行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

对于Docker版本1.3之前的情况:
docker run -p 5900 creack/firefox-vnc x11vnc -forever -usepw -create

2
我该如何使用VNC客户端远程查看这个?输入IP地址和端口似乎无法工作。 - user94154
18
首先,您需要检查已分配的端口(通过运行 docker inspect <容器ID> 或简单地运行 docker ps),然后使用刚刚找到的端口连接到您主机的IP地址。 - creack
10
crackfirefox-vnc镜像出现错误: 输入VNC密码:stty: 标准输入: 不适当的 ioctl 设备 fgets: 没有那个文件或目录 stty: 标准输入: 不适当的 ioctl 设备 x11vnc -usepw:找不到要使用的密码。 - alfonsodev
6
使用 Docker 运行 GUI 应用程序本文将介绍如何使用 Docker 来运行图形用户界面(GUI)应用程序。首先需要安装 Docker,然后在容器中运行所需的 GUI 应用程序和 X11 窗口系统。最后,通过连接到容器中运行的 X11 服务器来启动 GUI 应用程序并显示其输出。参考链接:http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/ - Dennis C
8
没有用户名,密码在答案中明确指出,任何VNC客户端都可以使用。在我的情况下,我喜欢本机的OSX客户端。(从Finder中按下command+K,然后连接到vnc://<docker IP>:<容器暴露端口>) - creack
显示剩余15条评论

218
Xauthority在新系统中成为一个问题。我可以在运行我的docker容器之前使用xhost +来丢弃任何保护,也可以传递一个准备好的Xauthority文件。典型的Xauthority文件是特定于主机名的。对于docker,每个容器都可以有不同的主机名(通过docker run -h设置),但即使将容器的主机名设置为与主机系统相同,也无法帮助解决问题。例如,xeyes会忽略魔术cookie并不向服务器传递任何凭据。因此,我们会收到错误消息“未指定协议 无法打开显示”。
可以以一种使主机名无关紧要的方式编写Xauthority文件。我们需要将身份验证系列设置为“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

18
注意:-v $XSOCK:$XSOCK -v $XAUTH:$XAUTH 可以简写为 -v $XSOCK -v $XAUTH - Piotr Aleksander Chmielowski
19
@Dirk:你可能想要用$DISPLAY替换:0。这意味着xauth nlist $DISPLAY | ...docker run -ti -e DISPLAY=$DISPLAY ...。通常X显示是:0,但并不总是(特别是如果你通过ssh -X进行连接)。 - johndodo
5
仅供参考:@ PiotrAleksanderChmielowski的评论对我没有用,我还必须添加--net = host - mguijarr
4
在Ubuntu 16.04上,xauth创建了/tmp/.docker.xauth文件,并设置为600权限。这导致容器内的xauth无法读取该文件。可以通过在容器内运行xauth list来验证。我已经在xauth nlist:0 | ...命令后添加了chmod 755 $XAUTH以解决此问题。 - Abai
4
以下对我有效(特别是添加-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 - kgibm
显示剩余11条评论

86
我刚刚发现了这篇博客文章,想与你分享,因为我认为这是最好的方法,并且非常容易实现。 http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/ 优点: + 在 Docker 容器中不需要 X 服务器 + 不需要 VNC 客户端/服务器 + 不需要 SSH 和 X 转发 + 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://dev59.com/el4b5IYBdhLWcg3wiSPV#28985715


我该如何在Windows 7上实现这个?我需要安装X服务器吗? - readytotaste
3
大多数答案都是针对Unix的,我想这只适用于Unix,直到Windows支持X服务器窗口系统。 - A. Binzxxxxxx
你认为在Windows上安装X服务器甚至将X服务器捆绑到我的Docker容器中是否可行? - readytotaste
2
我认为您还需要在 Dockerfile 中安装“apt-get -y install sudo”以创建“/etc/sudoers.d”文件夹。 - Alejandro Galera
2
可能需要通过 $ xhost + 允许来自任何主机的连接到 X。 - Bandoos
显示剩余4条评论

60

使用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的完全访问权限,因此只建议在您信任其中内容时使用。
注意:如果您关心安全问题,更好的解决方案是通过强制基于角色的访问控制限制应用程序。Docker实现了相当不错的隔离性,但它的设计目的不同。使用AppArmorSELinuxGrSecurity等工具来解决您的担忧。

5
您还需要使用类似xhost的工具允许其他主机访问X服务器。如果要完全开放它,请在主机上使用“xhost +”。 - Tully
3
@Tully 只需要运行 xhost +local 命令即可。不过最好将 ~/.Xauthority 文件放到容器中,以便容器可以进行身份验证。 - Aryeh Leib Taurog
6
在我的Ubuntu 14.04笔记本电脑上,使用docker 1.5时这个方法非常好用;但是现在在Ubuntu 15.04上,使用docker 1.6.2失败了,出现错误信息Can't open display: :0。你有什么想法吗? - cboettig
7
我使用了xhost +si:localuser:$USER来授权仅启动容器的用户。 - Nick Breen
3
@AryehLeibTaurog 我认为你的命令 xhost +local: 缺少尾随冒号。没有冒号,该命令在Ubuntu 16.04上失败了。 - TmTron
显示剩余6条评论

28

OSX

Jürgen Weigert的回答对于Ubuntu系统是最好的解决方案,但是在OSX系统中,Docker运行在VirtualBox中,因此需要进行一些额外的工作才能使其正常运行。

我使用以下附加组件使其正常运行:

  1. Xquartz(OSX不再提供X11服务器)
  2. 使用socat进行套接字转发(brew install socat)
  3. 编写BASH脚本以启动容器

我希望用户能够提供评论来改进这个解决方案,在我打算仅在本地运行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

我能用这种方法使xeyes和matplotlib工作。

Windows 7+

在Windows 7+上使用MobaXterm会更容易一些:

  1. 安装MobaXterm for windows
  2. 启动MobaXterm
  3. 配置X服务器: Settings -> X11 (tab) -> 将X11 Remote Access设置为full
  4. 使用这个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

xeyes running on PC


我不理解你说的bash脚本是什么意思 - 我该如何在Windows上运行它? - deller
@deller 我在Windows上进行软件开发,使用GIT,因此我可以使用GIT-bash shell。 - Nick
我按照步骤进行操作,但是出现了“错误:环境中未设置XDG_RUNTIME_DIR”和“错误:无法打开显示器:VAIO:0.0”。你是否遇到过类似的情况? - user3275095
2
我遇到了一个与用户未找到有关的错误,即“passwd文件中没有匹配的条目”。有什么线索吗? - readytotaste
@Nick,Dockerfile需要做哪些修改?就像之前的评论中提到的那样,我也遇到了同样的错误:“无法找到用户<username>:在passwd文件中没有匹配条目。” - ai2ys
@ai2ys,我想我一定已经将用户添加到容器中了。 - Nick

27
您还可以使用subuser:https://github.com/timthelion/subuser 这使您可以将许多GUI应用程序打包到Docker中,目前已经测试了Firefox和Emacs。但是,对于Firefox,WebGL无法正常工作。Chromium则根本无法工作。
编辑:声音可以正常工作!
编辑2:自我发布以来,subuser已经取得了很大进展。现在我已经有了一个网站 subuser.org,还有一种新的安全模型可以通过XPRA桥接连接到X11

3
请注意,子用户仍然非常新且相对未经测试。如果您遇到任何问题,请提交错误报告! - timthelion
如果可能的话,我会避免使用X11。您的绝杀应用将是在Docker中运行Tor代理,并在子Docker中运行一个带有插件的完整浏览器,这样防火墙等就会强制所有网络流量通过Tor Docker。由于可以放行富媒体内容,因此在Web可用性方面,这将比当前的Tor浏览器套件更加出色。 - Will
1
你是否遇到了X11安全问题?还是你想让它在Windows上工作?或者你想让它远程工作?以上所有情况都是吗?我认为使用VNC使其工作是完全可能的(尽管我不会将其作为默认方法,因为它增加了对VNC的依赖)。使子用户远程工作并没有真正意义。还有这个:https://github.com/rogaha/docker-desktop,但从错误报告来看,xpra在实际生活中可能无法使用。 - timthelion

25

如其他回答所述,共享主机显示号:0有两个缺点:

  • 由于一些X安全漏洞,它会破坏容器隔离性。例如,使用xevxinput进行键盘记录是可能的,并且可以使用xdotool远程控制主机应用程序。
  • 由于缺少X扩展MIT-SHM的共享内存,应用程序可能存在渲染故障和错误的RAM访问。 (也可以通过降级选项--ipc=host来解决)。

以下是一个运行docker镜像在Xephyr中的示例脚本,解决了这些问题。

  • 由于docker应用程序在嵌套的X服务器上运行,因此它避免了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加速、网络摄像头和打印机共享等功能。


19
这里有一个轻量级的解决方案,避免在容器中安装任何X服务器、VNC服务器或sshd守护进程。虽然简单易懂,但它在安全性和隔离性方面存在缺陷。
它假设您使用带有X11转发的ssh连接到主机。
在主机的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

2
这对于可信的应用程序非常有用。但是,对于任何类型的沙盒,您都要避免使用X-forwarding。 - Will
1
如果您不想将整个主目录挂载到容器中,只需挂载 .Xauthority 文件本身即可:-v $HOME/.Xauthority:/root/.Xauthority -e XAUTHORITY=/root/.Xauthority - Robert Haines
2
不必更改X11UseLocalhost,您也可以在docker run命令中使用附加选项--net=host(在此处找到)来解决问题。 - ingomueller.net
“--net=host” 是一个不好的想法,因为现在如果你在容器中打开一个端口,它也会在主机上打开... - MrR

17

虽然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会话的十六进制密钥。

所以基本步骤如下:

  • 提取用户当前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服务器将其与其本地副本进行比较。

如果您在主机和容器上运行具有相同Xauthority文件的xauth列表,则会列出不同的条目。-> 如果您确实看到了这一点,则这是一个错误。 在远程计算机上运行的SSH服务器模拟自己的X服务器。-> 不,它没有。它只在远程端打开TCP端口并将流量转发到本地端,在那里需要处理它的X服务器。 - Artur Opalinski

13

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/


3
我发现实际上不需要传递/tmp/.X11-unix套接字,只需挂载.Xauthority并使用--net=host即可正常工作。 - CMCDragonkai
3
现在这是唯一可行的解决方案。使用 "/tmp/.X11-unix" 作为卷已不再起作用,因为 Docker 会悄悄地拒绝来自粘性目录的挂载。 - Christian Hujer
1
我认为这取决于你使用的发行版。在CentOS上,你肯定可以绑定挂载X11 Unix套接字。同时,理解--network=host的作用也很重要。它会给予你的容器完全访问主机网络栈的权限,这可能是不可取的,具体取决于你想做什么。如果你只是在桌面上尝试运行容器化GUI,那么这就无关紧要了。 - orodbhen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接