三 文章首页 实时留言 网络邻居 开往 虫洞
返回

在 podman 运行 GUI 应用的经验总结

toolbox 和 Distrobox 不好定制?那就自己来吧。
2026-06-02 16:13:11
分类: Linux 标签: 容器

尝试过了很多容器方案,如 toolbox、bwrap、systemd-nspawn、docker、以及 Distrobox,发现都各有优势和缺点,像 toolbox 和 distrobox 是比较好上手的一种,但他们因为过于开箱即用默认挂载宿主很多东西(如 home/user ),有时不太好定制,此时我就得从更基础的 podman 开始定制。

先上完整脚本:

create_box.sh

#!/bin/bash
xhost +local:

PROXY_SOCKET="$XDG_RUNTIME_DIR/podman-bus-proxy"

podman run -it \
--dns "8.8.8.8" \
--env DISPLAY="$DISPLAY" \
--env XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
--env PULSE_SERVER="unix:$XDG_RUNTIME_DIR/pulse/native" \
--env DBUS_SESSION_BUS_ADDRESS="unix:path=$PROXY_SOCKET" \
--env XMODIFIERS="@im=fcitx" \
--env QT_IM_MODULE="fcitx" \
--env GTK_IM_MODULE="fcitx" \
--env WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
--volume /tmp/.X11-unix:/tmp/.X11-unix:ro \
--volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro \
--volume $XDG_RUNTIME_DIR:$XDG_RUNTIME_DIR:z \
--device /dev/kfd:/dev/kfd \
--device /dev/dri:/dev/dri \
--volume /run/udev:/run/udev:ro \
--volume /dev/shm:/dev/shm \
--volume /dev/input:/dev/input:ro \
--volume /etc/resolv.conf:/etc/resolv.conf:ro \
--network host \
--hostname $1 \
--name $1 \
--user root \
--group-add render \
--group-add video \
--group-add input \
--privileged \
--security-opt seccomp=unconfined \
--security-opt label=disable \
--userns=keep-id \
$2 \
/bin/bash

create_poxy.sh

xhost +local:

# 1. 在 XDG_RUNTIME_DIR 下生成一个代理 socket 路径
PROXY_SOCKET="$XDG_RUNTIME_DIR/podman-bus-proxy"

if [ -e "$PROXY_SOCKET" ]; then
    echo '已经存在'
else
    # 2. 启动 xdg-dbus-proxy,开启过滤:只放行 fcitx xdg-open 相关的通信,拦截其他所有服务(如 Portal)
    xdg-dbus-proxy "unix:path=$XDG_RUNTIME_DIR/bus" "$PROXY_SOCKET" \
        --filter \
        --own=org.fcitx.* \
        --talk=org.fcitx.* \
        --call="org.freedesktop.portal.Desktop=org.freedesktop.portal.OpenURI.*@/org/freedesktop/portal/desktop" &
    PROXY_PID=$!
fi

一般我会通过 create_box.sh <容器名> <镜像名> 来创建一个 podman 容器,

再通过 create_poxy.sh 建立一个 $XDG_RUNTIME_DIR/bus 的代理(为什么要这样呢?因为可以自己过滤哪些需要的通信,通常每次进入系统只需要执行一次)

而基础发行版镜像可以随便选,如 ubuntu、fedora、Arch、Debian 随便你,我们都知道 podman 可以使用 docker hub 的镜像,所以你可以直接 podman pull docker.io/library/debian 之类的。

跑完 create_box.sh 后此时就会就可以用这个容器了。你可以在 podman ps -a 可以查看到。

以后管理容器也非常简单:

启动:podman start <容器>

停止:podman stop <容器>

执行程序:podman exec -it <容器> <命令行>

podman 一些基础命令就不细说了,可以自行了解。

下面我们聊以上脚本都干了什么。

create_box.sh 脚本

(1) 有关 xhost +local:


允许本地用户(UID 相同或者 root)访问 X server(显示图形界面)。这是 X11 的授权机制。

(2) PROXY_SOCKET="$XDG_RUNTIME_DIR/podman-bus-proxy"


$XDG_RUNTIME_DIR 通常是 /run/user/1000,是每个用户的运行时目录。 PROXY_SOCKET 我指向了一个自定义的 D-Bus 通信代理,防止容器直接访问宿主的 D-Bus,增加安全性。这里会跟 create_poxy.sh 脚本有联动。

(3) 运行图形应用


通常需要加入 $DISPLAY$WAYLAND_DISPLAY 变量、透传 /tmp/.X11-unix、以及 $XDG_RUNTIME_DIR/dev/dri,主要是为了保证容器里的 x11/wayland GUI 能显示在宿主的屏幕上。

这里 /dev/dri 为了用于 GPU 加速

x11 应用通常走 /tmp/.X11-unix

wayland 应用通常是 $XDG_RUNTIME_DIR/wayland-0

这里我整个映射 $XDG_RUNTIME_DIR 主要是为了省事,因为后续还有很多通信需要走这里。

(4) 中文输入法


通常我们需要容器也拥有相关变量,并且在容器内也安装了 fcitx5 框架下,可以在容器内调用宿主的 fcitx5(前提是必须有透传 $XDG_RUNTIME_DIR 相关)

--env XMODIFIERS="@im=fcitx" \
--env QT_IM_MODULE="fcitx" \
--env GTK_IM_MODULE="fcitx" \

(5) /run/dbus/system_bus_socket


允许容器访问宿主 system bus(只读),主要是硬件或系统服务。

(6) /dev/kfd


如果你是 amd 的硬件,并且可能需要用到 rocm 之类的东西,这个是必须要的,用于 GPU 计算。

(7) /run/udev


在某些情况下,我们需要读取宿主的设备信息,几乎是必须的。

(8) /dev/shm


用于共享内存,x11 或 浏览器缓存这块很有用。

(9) /dev/input


输入设备(键盘、鼠标、触摸板)等的访问

(10) --network host


使用宿主网络, /etc/resolv.conf :只是为了 DNS,保证容器可以正常解析域名

(11) PULSE_SERVER 相关


为了能听到声音,必须得把 unix:$XDG_RUNTIME_DIR/pulse/native 弄进容器。

(12) 用户和权限


--user root \
--group-add render \
--group-add video \
--group-add input \
--privileged \
--security-opt seccomp=unconfined \
--security-opt label=disable \
--userns=keep-id \

这里的权限取决于自己的需求。

  • --user root:容器内使用 root 用户。
  • --group-add ...:添加 GPU / 视频 / 输入设备访问权限。
  • --privileged:打开所有权限(几乎等于 root 宿主)。
  • --security-opt seccomp=unconfined:关闭 seccomp 限制,允许系统调用。
  • --security-opt label=disable:关闭 SELinux 标签限制。
  • --userns=keep-id:保持宿主 UID/GID 对应,防止权限错乱。

create_proxy.sh

这个脚本是我最初是为了 启动 D-Bus 代理,限制容器能访问的服务。使用 xdg-dbus-proxy 的方式来实现。

  • --filter:启用访问控制
  • --own=org.fcitx.*:允许容器注册和使用 fcitx
  • --talk=org.fcitx.*:允许容器访问 fcitx
  • --call=org.freedesktop.portal.Desktop=org.freedesktop.portal.OpenURI.*@/org/freedesktop/portal/desktop:允许 xdg-open 调用,通常用于容器应用打开链接时让宿主的浏览器进行目标跳转

最后在每个容器中 /usr/local/bin/xdg-open 我用了自定义的 xdg-open :

#!/bin/bash

# 获取 URL 参数
URL="$1"

# 检查是否为空
if [ -z "$URL" ]; then
    echo "Usage: xdg-open <url>"
    exit 1
fi

# 补全协议(如果是 blog.glumi.cn 这种,补全为 http)
if [[ ! "$URL" =~ ^[a-zA-Z0-9]+:// ]]; then
    URL="http://$URL"
fi

# 核心:通过 D-Bus 调用宿主机的 Portal 接口
# 这里我们使用 busctl,它是 systemd 自带的,通常容器里都有
busctl --user call \
    org.freedesktop.portal.Desktop \
    /org/freedesktop/portal/desktop \
    org.freedesktop.portal.OpenURI \
    OpenURI "ssa{sv}" "" "$URL" 0

关于容器应用在宿主桌面上的快捷入口

总是通过命令行启动程序多少会有点麻烦,所以可以将一些容器应用的启动写成 .desktop 文件

通常写在 /usr/share/applications/~/.local/share/applications/

[Desktop Entry]
Version=1.0
Type=Application
StartupWMClass=hello
Terminal=false
Exec=podman exec .....
Name=Name of Application
Icon=/path/to/icon

其中 Exec 是所执行的程序地址(可以包含参数), Terminal 指明是否在执行一个终端程序, Name 也就是该应用程序的名称, version 版本号,Icon 应用程序图标,StartupWMClass 通常是窗口的类名(有时定位应用很有用,一些桌面环境dock栏识别图标会认这个)