Alternatives to ssh X11-forwarding for Docker containers
Asked Answered
S

3

64

I am running a Docker container mainly as an isolated development environment for the R language. (The usage of R here is orthogonal to the rest of the post, i.e. you can just assume any generic program that can run in a repl-session.) A lot of times this will involve doing stuff like plotting, making graphics and so on; and I need to look at these. Hence, I would prefer to have the option of displaying graphics I created in my container. Here is how I do this so far. First I create a Dockerfile. Leaving out the trivial steps the ones most relevant are:

# Set root passwd 
RUN echo "root:test" | chpasswd

# Add user so that container does not run as root 
RUN useradd -m docker 
RUN echo "docker:test" | chpasswd 
RUN usermod -s /bin/bash docker 
RUN usermod -aG sudo docker 
ENV HOME /home/docker

RUN mkdir /var/run/sshd 
RUN mkdir -p /var/log/supervisor

# copy servisord.conf which lists the processes to be spawned once this 
# container is started (currently only one: sshd) 
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

EXPOSE 22 
CMD ["/usr/bin/supervisord"]

I build the image and then start the container by using:

docker run -d -p 127.0.0.1:5000:22 -h ubuntu-r -v /home/chb/files/Data:/home/docker/Data -P --name="rdev" ubuntu-r

and can then ssh into my container:

ssh -X docker@localhost -p 5000.

This will give me what I want. But I would like to know if there is another more resource friendly way of getting graphics/GUI output from a container? (I'd prefer, if possible, solutions would not involve vnc.)

Selfacting answered 13/8, 2014 at 8:54 Comment(4)
you should have a look here: #16297253Popeyed
Possible duplicate of Can you run GUI apps in a docker container?Popeyed
Related: How to make Xvfb display visible? & Using VNCserver + GUI app + Virtual Display in Docker.Naoma
ssh -X docker@localhost -p 5000 asks for a password. As I have tried "tcuser" does not workJacobite
S
95

There is a nice and semi-easy way of getting graphical output from a Docker container without having to run an sshd daemon inside of the container. Docker can provide bare metal performance when running a single process which in this case is supposed to be R. Running an sshd daemon will, marginal as it may be, introduce additional overhead. This is not made better by running the sshd daemon as a child process of the supervisor daemon. Both can be dispensed with when one makes good use of bind mounts. After building the image from which the container is supposed to be run we start an interactive container and bind mount the /tmp/.X11-unix folder into it. I will state the complete command and explain in detail what it does:

docker run -i -t --rm \

  • -i sets up an interactive session; -t allocates a pseudo tty; --rm makes this container ephemeral

-e DISPLAY=$DISPLAY \

  • sets the host display to the local machines display (which will usually be :0)

-u docker \

  • -u specify the process should be run by a user (here docker) and not by root. This step is important (v.i.)!

-v /tmp/.X11-unix:/tmp/.X11-unix:ro \

  • -v bind mounts the X11 socket residing in /tmp/.X11-unix on your local machine into /tmp/.X11-unix in the container and :ro makes the socket read only.

--name="rdev" ubuntu-r R

  • --name="" specify the name of the container (here rdev); the image you want to run the container from (here ubuntu-r); the process you want to run in the container (here R). (The last step of specifying a process is only necessary if you have not set a default CMD or ENTRYPOINT for your image.)

After issuing this command you should be looking at the beautiful R start output. If you were to try demo(graphics) to see if graphical output is already working you would note that it is not. That is because of the Xsecurity extension preventing you from accessing the socket. You could now type xhost + on your local machine and try demo(graphics) in your container again. You should now have graphical output. This method however, is strongly discouraged as you allow access to your xsocket to any remote host you're currently connected to. As long as you're only interacting with single-user systems this might be somehow justifiable but as soon as there are multiple users involved this will be absolutely unsafe! Hence, you should use a less dangerous method. A good way is to use the server interpreted

xhost +si:localuser:username

which can be used to specify a single local user (see man xhost). This means username should be the name of the user which runs the X11 server on your local machine and which runs the docker container. This is also the reason why it is important that you specify a user when running your container. Last but not least there is always the more complex solution of using xauth and .Xauthority files to grant access to the X11 socket (see man xauth). This however will also involve a little more knowledge how X works.

The positive effect this can have can be seen in the number of processes that need to be run in order to achieve what is wanted.

(1) with supervisor and sshd running in the container:

UID                 PID                 PPID                C                STIME               TTY                 TIME                CMD
root                4564                718                 1                18:16               ?                   00:00:00            /usr/bin/python /usr/bin/supervisord
root                4576                4564                0                18:16               ?                   00:00:00            /usr/sbin/sshd

when logged in via ssh and running R:

UID                 PID                 PPID                C                 STIME               TTY                 TIME                CMD
root                4564                718                 0                 18:16               ?                   00:00:00            /usr/bin/python /usr/bin/supervisord
root                4576                4564                0                 18:16               ?                   00:00:00            /usr/sbin/sshd
root                4674                4576                0                 18:17               ?                   00:00:00            sshd: docker [priv]   
chb                 4725                4674                0                 18:18               ?                   00:00:00            sshd: docker@pts/0
chb                 4728                4725                1                 18:18               pts/0               00:00:00            -bash

(2) with bind mount method:

UID                 PID                 PPID                C                 STIME               TTY                 TIME                CMD
chb                 4356                718                 0                 18:12               pts/4               00:00:00            /usr/local/lib/R/bin/exec/R --no-save --no-restore
Selfacting answered 15/8, 2014 at 21:13 Comment(7)
Great stuff, thank you :-) How does your solution compare to that one: olivier.barais.fr/blog/posts/2014.08.26/… (different xhost command, no -u option)Bluebell
Thanks. :) In short: She/he is granting access only to a specific trusted docker container by using the containers hostname. I on the other hand grant access to a trusted user and thereby all containers she/he is starting. This works because the uid in the container is identical to the one on the host.Selfacting
Ok, I get it, cheers =) If I run multiple containers, then it might be more convenient to trust the user. But I'm guessing it's also less secure. What about the -u option? You solution seems to work without it; am I missing something?Bluebell
This has two(?) reasons: (1) The -u option just adds another layer of security. The docker daemon for now runs as root on the host. So should a process in a container escape to the host it can do a lot of damage. This is somehow migitated if the processes in the container run as a user process. (2) Granting root access to the x-socket is not a good idea. To phrase it differently: The principle of least privilege should be applied.Selfacting
This does not appear to be a copy and pasteable answer. The -u command gives a "Error response from daemon: Cannot start container X: [8] System error: Unable to find user docker" error. This is despite my current running user being 'docker' and there also being a 'docker' user defined in the Dockerfile.Buttons
as the mount point /tmp/.X11-unix:/tmp/.X11-unix:ro is linux dependent this will only work when running this container on a linux machine, correct? Is there an equivalent work around for a docker that needs to be run on linux, windows, and mac?Fusiform
I run docker run -it --net=host --rm \ -e DISPLAY \ --volume=$HOME/.Xauthority:$HOME/.Xauthority \ --privileged \ -v $XSOCK -v $XAUTH \ image_name and on running xeyes in the container I get: No protocol specified Error: Can't open display: :0Burget
P
4

Here is clearly the best solution I found until now:

https://mcmap.net/q/45677/-can-you-run-gui-applications-in-a-linux-docker-container (all credit goes to Jürgen Weigert)

Advantages:

  • Inside docker, the UID is not relevant (but I still recommend to not use root)
  • On the host, you don't have to change security settings (xhost +)
Palmer answered 16/11, 2016 at 15:31 Comment(0)
D
0

Based on lord.garbage's answer we can forward the host's X11 socket. And similarly we can create a user at docker image creation time as per Jürgen Weigert's very nice idea shared above (thanks Hugo/Daniel.) However, what if we don't want to modify xhost ACL or create an image that is statically bound to one user's credentials? There is one additional refinement that we can make:

When running your container forward user-id/group from host shell:

XSOCK=/tmp/.X11-unix
docker run -it --rm \
    -e HOST_USER=$(id -u) \
    -e HOST_GROUP=$(id -g) \
    -e HOST_USER_NAME=$(whoami) \
    -e DISPLAY=$DISPLAY \
    -v $XSOCK:$XSOCK \
    my_container:latest

Create wrapper script and set it as your container's ENTRYPOINT. In your script create a new user with the host's user-id/group-id then use sudo to run your target application "as host":

groupadd -o -g $HOST_GROUP docker
useradd -d /home/$HOST_USER_NAME -s /bin/bash -u $HOST_USER -g docker $HOST_USER_NAME
sudo -u $HOST_USER_NAME bash -c "<some bash commands etc.>"

Since program will execute with correct user, access control changes are unnecessary. Thanks to Jürgen Weigert's great idea, and this small refinement we have a solution that doesn't require xhost and will run anywhere.

Decanter answered 16/9, 2021 at 21:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.