it is possible to run gui applications in docker containers, at least on linux;

this problem mainly consists of two subproblems: connection and authentication;

x server connection

x uses a client-server architecture; the x server on host listens on a socket; gui applications on guest must connect to this x server socket to draw windows on host display;

depending on x server configuration, this socket can be one of these types:

  • an abstract unix domain socket: usually @/tmp/.X11-unix/X0;

  • a pathname unix domain socket: usually /tmp/.X11-unix/X0;

  • a tcp socket; usually disabled with --nolisten tcp;

to see what this socket really is, run on host:

$ netstat -npl | grep Xorg

on most personal computers, this would be a unix domain socket; to be precise, there might be both abstract and pathname unix domain sockets, and we can use either of them;

use pathname socket

we do pathname socket first, because this is simpler; to make this socket file accessible on guest, we can bind mount it onto guest;

the docker run command has a -v, --volume option to do this:

$ docker run -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 ...

use abstract socket

abstract sockets are harder to deal with; man 7 network_namespaces points out the most important thing:

In addition, network namespaces isolate the UNIX domain abstract socket namespace (see unix(7)).

this means, guest and host cannot communicate via abstract sockets when they are located in distinct network namespaces (which is the normal case); to facilitate such communications, we have 2 solutions:

  1. use host network driver:

    $ docker run --net=host ...
    

    the host network driver puts guest and host in the same network namespace;

  2. use bridge network driver, and relay abstract sockets via tcp sockets;

    the bridge network driver puts each guest in its own network namespace but allows guest-guest and host-guest communication via veth pairs; sadly, these veth pairs do not help unix domain sockets;

    but we can help ourselves; specifically, we run one socat instance on host and one on guest:

    • on host:

      $ socat TCP-LISTEN:<port>,reuseaddr,fork,bind=<host> ABSTRACT-CONNECT:/tmp/.X11-unix/X0
      
    • on guest:

      $ socat ABSTRACT-LISTEN:/tmp/.X11-unix/X0,fork TCP-CONNECT:<host>:<port>
      

    in both commands:

    • <host> is the ip address of network bridge docker0; we can find this address by running on host:

      $ ip addr show dev docker0
      
    • <port> is an arbitrary port number (>=1024);

    together, these commands establish a tunnel between @/tmp/.X11-unix/X0 on host and @/tmp/.X11-unix/X0 on guest, leveraging available network stacks;

x server authentication

now we have the connection from gui applications on guest to x server socket on host; next topic is authentication: such gui applications must be authenticated to use the x server on host;

most x servers have two ways of authentication:

  • xhost: host list mechanism;

  • xauth: magic cookie mechanism;

we can use either way to authenticate; xhost is simpler, but xauth is more secure; we can, but do not have to, use both at the same time;

xhost authentication

xhost controls access to x server using a host-based mechanism; when we run command:

$ xhost

the output usually contains a line (<user> is a user name):

SI:localuser:<user>

this line allows connection to x server by local user <user>;

assuming we are running gui applications on guest with user root; we need to tell x server on host to accept connections coming from user root; to do so, we run this command on host:

$ xhost +SI:localuser:root

now if we run this again:

$ xhost

we will find another line:

SI:localuser:root

this line allows connection to x server by local user root;

note that we are taking another assumption here: user namespaces are not being isolated; see userns-remap for details; in short, this means the root user on guest is the same root user on host; should this not be the case, we need to find out the real user on host then amend the above command accordingly;

if we would rather grant access to all local connections, we can run:

$ xhost +local:

as a last resort, we can disable access control to grant access to everyone by running:

$ xhost +

but this is obviously not recommended;

xauth authentication

this works like a browser cookie: a connection is allowed if the client proves knowledge of a secret; such secret is called a magic cookie;

xauth is the tool to edit and display magic cookies:

  • display xauth information (authorization file, etc.):

    $ xauth info
    
  • display all magic cookies:

    $ xauth [n]list
    
  • extract magic cookies for specified displays to file:

    $ xauth [n]extract filename displayname
    
  • merge magic cookies from file:

    $ xauth [n]merge filename
    

the authorization file serves as the magic cookie database; this file can be set through command-line option, environment variable or default to ~/.Xauthority; create this file empty if it does not exist yet; doing so helps avoid some error messages;

some xauth commands have [n] versions; these are useful when their input and output need to be transmitted in non-binary form, such as during copy-paste;

to authenticate connection from guest to host, we first extract magic cookies on host then merge them on guest;

set x display name

gui applications need to know which x display to use; this is controlled by an environment variable DISPLAY; this environment variable should have been set on host, so we just need to propagate it to guest:

$ docker run -e "DISPLAY" ...

examples

after these long explanations, we can finally put things together and give some working examples; these examples are done with a container started from ubuntu image, as root user; when they run successfully, we would see rolling eyes in a small window;

when authentication is needed, these examples prefer xauth to xhost;

some examples have <copy> and <paste> commands; these are not real commands but places where manual intervetions are necessary; basically, we need to write down the data on <copy> and provide the same data on <paste>; if we can ssh from host into guest, then we can save some copy-paste by running this on host:

$ xauth extract - "$DISPLAY" | ssh <guest> xauth merge -

pathname socket

  1. on host:

    $ xauth nextract - "$DISPLAY" | <copy>
    $ docker run -it -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 -e "DISPLAY" ubuntu bash
    
  2. on guest:

    $ apt-get -y update
    $ apt-get -y install xauth x11-apps
    $ touch ~/.Xauthority
    $ <paste> | xauth nmerge -
    $ xeyes
    

this solution keeps network isolation and is not complicated;

abstract socket, host network driver

  1. on host:

    $ xauth nextract - "$DISPLAY" | <copy>
    $ docker run -it --net=host -e "DISPLAY" ubuntu bash
    
  2. on guest:

    $ apt-get -y update
    $ apt-get -y install xauth x11-apps
    $ touch ~/.Xauthority
    $ <paste> | xauth nmerge -
    $ xeyes
    

this solution is the simplest, but breaks network isolation;

abstract socket, bridge network driver

  1. on host:

    $ ip addr show dev docker0
    $ socat TCP-LISTEN:<port>,reuseaddr,fork,bind=<host> ABSTRACT-CONNECT:/tmp/.X11-unix/X0
    $ xauth nextract - "$DISPLAY" | <copy>
    $ docker run -it -e "DISPLAY" ubuntu bash
    
  2. on guest:

    $ apt-get -y update
    $ apt-get -y install x11-apps socat
    $ socat ABSTRACT-LISTEN:/tmp/.X11-unix/X0,fork TCP-CONNECT:<host>:<port>
    $ touch ~/.Xauthority
    $ <paste> | xauth nmerge -
    $ xeyes
    

this solution can come into handy when xhost on host has:

SI:localuser:<user>

if so, the steps can be simplified:

  1. on host:

    $ ip addr show dev docker0
    $ socat TCP-LISTEN:<port>,reuseaddr,fork,bind=<host> ABSTRACT-CONNECT:/tmp/.X11-unix/X0
    $ docker run -it -e "DISPLAY" ubuntu bash
    
  2. on guest:

    $ apt-get -y update
    $ apt-get -y install x11-apps socat
    $ socat ABSTRACT-LISTEN:/tmp/.X11-unix/X0,fork TCP-CONNECT:<host>:<port>
    $ xeyes
    

this saves us the effort of setting xauth, because the connection to x server is actually made by socat on host; as long as this socat is running as same user, no xauth work on guest needs to be done; however, beware that the relay is unsecured, which means anyone having access to its listening port would have access to x server, too; consider using firewalls to secure the listening port;