Alternatives to ssh X11-forwarding for Docker containers
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
.)
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 (heredocker
) and not by root. This step is important (v.i.)!
-v /tmp/.X11-unix:/tmp/.X11-unix:ro \
-
-v
bind mounts theX11
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 (hererdev
); the image you want to run the container from (hereubuntu-r
); the process you want to run in the container (hereR
). (The last step of specifying a process is only necessary if you have not set a defaultCMD
orENTRYPOINT
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
Here is clearly the best solution I found until now:
https://stackoverflow.com/a/25280523/1353930 (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 +
)