Linux GUI on OS X using Docker for Mac

Mon Jun 13, 2016

Info on running linux X11 GUI apps on MacOS.

Docker for Mac doesn’t work with sharing the X11 socket. So we need to use socat, which is a command line utility that establishes two bidirectional byte streams and transfers data between them. We use this to allow the X11 traffic to occur between the linux docker host VM, and MacOS.

Installation of Prerequitsites

Install home brew
Install XQartz
brew install socat

Setting up the container

JEFFREYs-MBP:zim-docker jliu$ cat Dockerfile
FROM ubuntu:14.04

RUN apt-get update && apt-get install -y xterm && apt-get install -y firefox && apt-get install -y zim && rm -rf /var/cache/apt/

# Replace 1000 with your user / group id
RUN export uid=501 gid=20 && \
    mkdir -p /home/developer && \
    echo "developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash" >> /etc/passwd && \
    echo "developer:x:${uid}:" >> /etc/group && \
    echo "developer ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/developer && \
    chmod 0440 /etc/sudoers.d/developer && \
    chown ${uid}:${gid} -R /home/developer

USER developer
ENV HOME /home/developer
#CMD /usr/bin/firefox -no-remote
#CMD /usr/bin/zim

Execution

JEFFREYs-MBP:~ jliu$ socat TCP-LISTEN:6000,reuseaddr,fork,range=192.168.1.239/32 UNIX-CLIENT:\"$DISPLAY\"

JEFFREYs-MBP:~ jliu$ env |grep DISPLAY
DISPLAY=/private/tmp/com.apple.launchd.R53BebLmH3/org.macosforge.xquartz:0



JEFFREYs-MBP:zim-docker jliu$ docker run --rm -ti -e DISPLAY=192.168.1.239:0 zim sh

NOTE: next step is to add volume for zim notebook directory

docker run --rm -ti -e DISPLAY=192.168.1.239:0 -v /Users/jliu/test zim sh


NOTE: zim does not handle updates on the host filesystem well. You need to reload zim for changes to be seen.

NOTE: hugo does not have this problem and is able to detect changes as it normally would. (sporadically works and sometimes it doesn’t work)

Oct 9, 2016 - Docker network issue

Ran into an issue in which the container could no longer X display to the host.

Narrowed down the issue to the fact that the container could no longer connect to the Host on port 6000.

This was due to overlapping network ranges from Docker networks with the private RFC1928 network my laptop was on.

The Solution

Clean up your local Docker network definitions to address the possiblity that these RFC1918 ranges might overlap/conflict with the network you’re currently on.

First and foremost, Docker does not clean up network allocations. When you start a new docker-compose project, Docker will automatically allocate a new docker network. After you run docker-compose rm -f the containers are removed, but the network is not touched. This is similar to how Docker images need to be manually removed independently from Docker containers.

It seems that some of the networks were allocated by Docker on the RFC1918 172.16.x.x/12 space with /16 blocks, and when the 172.16.x.x/12 ranges were used up (which is fairly easy when allocating with /16 blocks) Docker allocated a new 192.168.2.x/20 network block. This network overlapped with my home network (192.168.1.x/24) - the result: the container could not reach my Host.

List current Docker networks

docker network ls
docker network rm network1 network2

10/15/2016 NOTE: the ipcalc shows the issue is related to overlapping network range - see below. Mystery solved.

 $ docker network create test --subnet 192.168.2.0/24
25012fbb11d832895681d00225cd7381a0299101c08550c25b4e251973748ba0
jliu@JEFFREYs-MBP ~/git/usedgoodies.com/content/posts (develop) $ docker network inspect test
[
    {
        "Name": "test",
        "Id": "25012fbb11d832895681d00225cd7381a0299101c08550c25b4e251973748ba0",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.2.0/24"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

$ docker run --name zim -h zim -ti --network test -e DISPLAY=192.168.1.239:0 -v /Users/jliu/git:/home/developer/git zim:0.2 sh
$ docker network create test --subnet 192.168.2.0/20
771deb694b1184c6b2ddf4c06e457a05a742f00a514230686562bc10fc0080b2
jliu@JEFFREYs-MBP ~/git/usedgoodies.com/content/posts (develop) $ docker network inspect test
[
    {
        "Name": "test",
        "Id": "771deb694b1184c6b2ddf4c06e457a05a742f00a514230686562bc10fc0080b2",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.2.0/20"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]


jliu@JEFFREYs-MBP ~/ael/docker $ docker exec -ti dockerzim_zim_1 ipcalc 192.168.2.0/20
Address:   192.168.2.0          11000000.10101000.0000 0010.00000000
Netmask:   255.255.240.0 = 20   11111111.11111111.1111 0000.00000000
Wildcard:  0.0.15.255           00000000.00000000.0000 1111.11111111
=>
Network:   192.168.0.0/20       11000000.10101000.0000 0000.00000000
HostMin:   192.168.0.1          11000000.10101000.0000 0000.00000001
HostMax:   192.168.15.254       11000000.10101000.0000 1111.11111110
Broadcast: 192.168.15.255       11000000.10101000.0000 1111.11111111
Hosts/Net: 4094                  Class C, Private Internet

Listing out the docker networks

jliu@JEFFREYs-MacBook-Pro ~ $ docker network inspect $(docker network ls -q) | | jq -r '.[].Name , .[].IPAM.Config'

or

for i in $(docker network ls -q); do docker network inspect $i | jq -r '.[].Name, .[].IPAM.Config'; done


Configuring a network within docker-compose.yml

NOTE: look at compose documentation on info with creating a custom network https://docs.docker.com/compose/networking/#specifying-custom-networks https://docs.docker.com/compose/compose-file/#network-configuration-reference http://stackoverflow.com/questions/35708873/how-do-you-define-a-network-in-a-version-2-docker-compose-definition-file https://www.linux.com/learn/docker-volumes-and-networks-compose

So to avoid this from recurring, you can provide a network definition within your docker-compose.yml file with a more limited network scope or one that does not conflict with the network you’re currently on.

version: '2'

services:
  app:
    image: busybox
    command: ifconfig
    networks:
      app_net:
        ipv4_address: 172.18.18.10

networks:
  app_net:
    driver: bridge
    driver_opts:
      com.docker.network.enable_ipv6: "false"
    ipam:
      driver: default
      config:
      - subnet: 172.18.18.0/24
        gateway: 172.18.18.1

References

8/1/2016 - a new tutorial http://blog.alexellis.io/linux-desktop-on-mac/

Original post which worked prior to Docker for Mac Beta: http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/

From May 21, 2016 - Explanation of why Docker for Mac Beta doesn’t work with sharing X11 socket https://forums.docker.com/t/socket-pipes-in-mounted-volumes-not-working/12861/2 There’s no time line for this to be implemented - so the recommendation is to use socat for now.

You need socat, which is a command line based utility that establishes two bidirectional byte streams and transfers data between them, and XQuartz - Apples version of the X server. Reference on socat
NOTE: the reference to tweak the security settings for XQuartz is not needed.

https://support.apple.com/en-us/HT201341

https://www.xquartz.org

https://forums.docker.com/t/solved-cant-run-gui-applications-under-security-constraints/13397/4 This contains a snippet which can grab your primary host interface IP address and then export the DISPLAY variable to the docker container