Bobby Battista
Bobby Battista

Reputation: 2075

How to view GUI apps from inside a docker container

When I try to run a GUI, like xclock for example I get the error:

Error: Can't open display: 

I'm trying to use Docker to run a ROS container, and I need to see the GUI applications that run inside of it.

I did this once just using a Vagrant VM and was able to use X11 to get it done.

So far I've tried putting way #1 and #2 into a docker file based on the info here: http://wiki.ros.org/docker/Tutorials/GUI

Then I tried copying most of the dockerfile here: https://hub.docker.com/r/mjenz/ros-indigo-gui/~/dockerfile/

Here's my current docker file:

# Set the base image to use to ros:kinetic
FROM ros:kinetic

# Set the file maintainer (your name - the file's author)
MAINTAINER me

# Set ENV for x11 display
ENV DISPLAY $DISPLAY
ENV QT_X11_NO_MITSHM 1

# Install an x11 app like xclock to test this
run apt-get update 
run apt-get install x11-apps --assume-yes

# Stuff I copied to make a ros user
ARG uid=1000
ARG gid=1000

RUN export uid=${uid} gid=${gid} && \
    groupadd -g ${gid} ros && \
    useradd -m -u ${uid} -g ros -s /bin/bash ros && \
    passwd -d ros && \
    usermod -aG sudo ros

USER ros
WORKDIR /home/ros

# Sourcing this before .bashrc runs breaks ROS completions
RUN echo "\nsource /opt/ros/kinetic/setup.bash" >> /home/ros/.bashrc

# Copy entrypoint script into the image, this currently echos hello world
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

Upvotes: 4

Views: 2900

Answers (2)

Xew4p
Xew4p

Reputation: 11

I assume that

  • host system is Linux
  • windowing api is X11 .

And I'll use podman but you can replicate by replacing podman with docker in every command, I just like podman and never used docker.

My System: Opensuse Tumbleweed with kde [Intel Optimus laptop]


Solution

Applications need to ask Xserver to display their GUI elements. And there's an authorization system designed for security.

This authorization system is called xauth. xauth usually stores session cookies inside /home/username/.Xauthority file(usually but not always).

check for the current cookies in use

xauth list

output will look like this,

localhost.localdomain/unix:0  MIT-MAGIC-COOKIE-1  99aaccf2d83177ddf581e2989ebbcea1
#ffff##:0  MIT-MAGIC-COOKIE-1  99aaccf2d83177ddf581e2989ebbcea1

in case the Xauthority is not located at the usual place, check which authority file is in use

xauth

output will look something like this is if standard file is not in use,

Using authority file /run/user/1000/xauth_Abcde

We need to supply these two things to our container,

  • MIT-MAGIC-COOKIE-1 - for now just think of this as a protocol or identifier. This is constant for every session.
  • 99aaccf2d83177ddf581e2989ebbcea1 - this 32 letter key is the session key or whatever fancy name you want to give it. Key is unique for each session.

Now to resolve issues related to display we need to do these five things,

1. Create a .Xauthority file if not present already inside container, as in our example (debian)

2. Add our session key and protocol to the .Xauthority file we created

3. Pass DISPLAY environment variable to container

4. Mount host Xserver socket(usually located at /tmp/.X11-unix) to container

5. Set network to host(required for display rendering)

These 5 steps will fix all issues related to display.


CONFIGURATION - Example Scenario

Example to run firefox with GUI via container.

TO-DO

1. create a .Xauthority file

done inside Containerfile RUN touch .Xauthority

2. add our session key and protocol to the .Xauthority file we created

both passed as environment variables

protocol set in Containerfile ENV PROTOCOL=MIT-MAGIC-COOKIE-1

session key passed as argument to podman run --env KEY=$(xauth list | sed '2,$d'| tr -d '\n' | tail -c 32) \(can't pass inside the Containerfile as constant because it changes session to session)

and then added to .Xauthority via CMD xauth add ${HOST}:0 $PROTOCOL $KEY from Containerfile

3. pass DISPLAY environment variable to the container

passed as argument to podman run --env DISPLAY \

4. mount Xserver socket(usually located at /tmp/.X11-unix) to container

passed as argument to podman run --mount type=bind,source=/tmp/.X11-unix,target=/tmp/.X11-unix,readonly \

5. set network type to host

configured while building the image podman build --network=host --tag guitest .

Containerfile

FROM debian:latest

ARG DEBIAN_FRONTEND=noninteractive

RUN apt update && apt upgrade
RUN apt install --no-install-recommends --yes firefox-esr pipewire pipewire-alsa pipewire-pulse ffmpeg xauth

ENV PROTOCOL=MIT-MAGIC-COOKIE-1

ENV HOME /home/def
ENV USER def

RUN useradd --create-home --home-dir ${HOME} -G audio,video ${USER} && chown -R ${USER}:${USER} ${HOME}

WORKDIR ${HOME}
USER ${USER}

RUN touch .Xauthority

CMD xauth add ${HOST}:0 $PROTOCOL $KEY && firefox

Build Command

podman build --network=host --tag guitest .

Run Container using the image we built

podman run -it --rm --name guiapp \
--env DISPLAY \
--env KEY=$(xauth list | sed '2,$d'| tr -d '\n' | tail -c 32) \
--mount type=bind,source=/tmp/.X11-unix,target=/tmp/.X11-unix,readonly \
guitest

BINGO ! firefox GUI in front of your eyes.


Disclaimer audio will not work in this example, Some extra steps needed to make audio work.

Upvotes: 0

BMitch
BMitch

Reputation: 264285

My personal preference is to inject the display variable and share the unix socket or X windows with something like:

docker run -it --rm -e DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -v /etc/localtime:/etc/localtime:ro \
  my-gui-image

Sharing the localtime just allows the timezone to match up as well, I've been using this for email apps.

The other option is to spin up a VNC server, run your app on that server, and then connect to the container with a VNC client. I'm less a fan of that one since you end up with two processes running inside the container making signal handling and logs a challenge. It does have the advantage that the app is better isolated so if hacked, it doesn't have access to your X display.

Upvotes: 3

Related Questions