Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ashnur/b710e2acd84af9440b57544c47f469c7 to your computer and use it in GitHub Desktop.
Save ashnur/b710e2acd84af9440b57544c47f469c7 to your computer and use it in GitHub Desktop.
Running GUI applications inside Docker containers

Let's start with the basics of how X Window System works. X uses client-server model. An X server program runs on a computer with a graphical display and communicates with various client programs (X clients). The X server acts as a go-between for the user and the client programs, accepting requests for graphical output from the client programs and displaying them to the user (display), and receiving user input (keyboard, mouse) and transmitting it to the client programs.

In X, the server runs on the user's computer, while the clients may run on remote machines. This terminology reverses the common notion of client–server systems, where the client normally runs on the user's local computer and the server runs on the remote computer. The X Window terminology takes the perspective that the X Window program is at the centre of all activity, i.e. the X Window program accepts and responds to requests from applications, and from the user's mouse and keyboard input. Therefore, applications (on remote computers) are viewed as clients of the X Window server program.

So, to run a GUI application in a docker container, you've got to provide a way for it to communicate to the X server running on the host. One way to go about it is to use host networking (--network host). In this case the container shares the host's networking namespace. I.e. container’s network stack is not isolated from the Docker host. Particularly, the container can connect to any servers running on the host.

Also, you've got to let the container authenticate to the X server. Again, one way to achieve this is to use cookie-based authentication. For that you've got to share the ~/.Xauthority file with the container (--volume ~/.Xauthority:/root/.Xauthority:ro).

And you've got to tell the container where the X server is running. For that the DISPLAY variable is used. Since the container is going to have access to the host's network namespace, you can simply pass the DISPLAY variable from the host into the container (--env DISPLAY).

As for Chrome we need --disable-gpu flag. Otherwise it won't start. --no-sandbox tells it that it's okay to run under root account:

[6:6:1214/141504.683039:ERROR:zygote_host_impl_linux.cc(89)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.

And --no-first-run makes it not show the Fisrt Launch popup.

chrome_cmd=(
    google-chrome
        --disable-gpu
        --no-sandbox
        --no-first-run
        https://google.com
)
docker run --rm \
    --network host \
    --volume ~/.Xauthority:/root/.Xauthority:ro \
    --env DISPLAY \
    cypress/browsers:node12.6.0-chrome77 \
    "${chrome_cmd[@]}"

If you decide to use cypress/included image, you've got to override the default entrypoint (--entrypoint ''):

chrome_cmd=(
    google-chrome
        --disable-gpu
        --no-sandbox
        --no-first-run
        https://google.com
)
docker run --rm \
    --network host \
    --volume ~/.Xauthority:/root/.Xauthority:ro \
    --env DISPLAY \
    --entrypoint '' \
    cypress/included:3.8.0 \
    "${chrome_cmd[@]}"

Or using the zenika/alpine-chrome image:

docker container run --rm \
    --network=host \
    --volume ~/.Xauthority:/home/chrome/.Xauthority:ro \
    --env DISPLAY \
    --entrypoint '' \
    zenika/alpine-chrome \
    chromium-browser --no-sandbox

As a bonus, here's how to take a screenshot of a browser running under xvfb-run:

./1.sh:

#!/usr/bin/env bash
set -eu
chrome_cmd=(
    google-chrome
        --disable-gpu
        --no-sandbox
        --no-first-run
        https://google.com
)
docker run --rm \
    -v $PWD:/d \
    -w /d \
    --entrypoint ./2.sh \
    cypress/browsers:node12.6.0-chrome77 \
    "${chrome_cmd[@]}"

./2.sh:

#!/bin/sh
xvfb-run sh -c '
    "$@" \
    & sleep 2 \
    && import -window root 1.png
' -- "$@"
./1.sh && eog 1.png

import is an ImageMagick utility that captures some or all of an X server screen, eog is an image viewer.

And how to run cypress tests in a docker container:

docker run -it --rm \
    --network host \
    -v ~/.Xauthority:/root/.Xauthority:ro \
    -e DISPLAY \
    -v $PWD:/e2e \
    -w /e2e \
    --entrypoint '' \
    cypress/included:3.6.1 \
    npx cypress open

P.S. If you do anything memory instensive with Chrome it might crash. That's because by default /dev/shm size in a docker container is 64MB. You can remedy it either by adding --disable-dev-shm-usage to Chrome, or --ipc=host to docker run. More on it here:

https://sondnm.github.io/blog/2018/09/08/i-just-learnt-about-/dev/shm/ https://peter.sh/experiments/chromium-command-line-switches/ https://docs.docker.com/engine/reference/run/#ipc-settings---ipc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment