<!--
  canonical-url: https://www.avonture.be/blog/docker-run-linux-gui
  published:     2024-09-06
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Docker - Run Graphical User Interfaces - Firefox, Chrome & GIMP

> Unlock the power of Docker! A step-by-step guide to running Linux Graphical User Interfaces (GUIs) like Firefox, Chrome, and GIMP in Docker containers.

---
![Docker - Run Graphical User Interfaces - Firefox, Chrome & GIMP](/img/v2/docker_gui.webp)

**TL;DR**

This follow-up article shows how to build custom Docker images (starting with a minimal `xeyes` example) that run Linux GUI apps — Firefox, Chrome, GIMP — as native windows on the host, by sharing the `DISPLAY` environment variable and the `/tmp/.X11-unix` socket with the container and granting X server access via `xhost +local:docker`.

In my [previous post](/blog/docker-gui-in-browser), I've illustrated how to start Firefox or GIMP in a browser. This was the first part of this series about graphical user interfaces because, until very recently, I didn't know it was possible to run GUIs with Docker and that's just amazing.

> **`GUI` stands for `Graphical User Interface`:**

So, using Docker, we can start Firefox or GIMP or even ... [Doom 2](https://hub.docker.com/r/classiccontainers/doom2). And if a single application isn't enough, [Start lubuntu Desktop in Docker](/blog/docker-lubuntu) gives you an entire desktop.

In this blog post, we'll create our own xeyes Docker image, then play with Firefox and Gimp.

## A first look

![xeyes under Docker](./images/xeyes_in_docker.webp)

Yes, it's true, it's useless, but wow! it's possible to run a GUI from a container and replicate the image in real time on our host machine. That's `xeyes` — a pair of eyes following your mouse cursor — running entirely inside a container, displayed as a native window.

## Why it works

* The container shares the host's `DISPLAY` environment variable, so it knows which X display to draw on.
* The host's `/tmp/.X11-unix` socket is mounted into the container, which is the actual channel X11 applications use to talk to the display server.
* `xhost +local:docker` grants the Docker socket permission to connect to the host's X server in the first place — without it, the connection is refused.

## Creating our own xeyes Docker image

Let's start with something really geeky.

Go to a temporary folder (f.i. `mkdir -p /tmp/xeyes && cd $_`) and create a file called `Dockerfile` with this content:

**Dockerfile**

```docker
FROM ubuntu:latest

RUN apt-get update && apt-get install -y --no-install-recommends x11-apps && \
    rm -rf /var/lib/apt/lists/*

CMD [ "xeyes" ]
```

Now, create the image by docker build like this: `docker build --tag cavo789/xeyes .` (replace `cavo789` by anything else like your pseudo).

Make sure you've a variable called `DISPLAY`. You can check this by running `printenv | grep DISPLAY`. If you don't have it, create the variable by running `export DISPLAY=:0` in the console.

Now, run `xhost +local:docker` in your console. That command grants permission to connect to an X server using the Docker socket. This means that applications running within Docker containers can display their graphical user interface (GUI) on the host system. The expected result is this text: `non-network local connections being added to access control list`

Now, simply run a container and make sure to share the `DISPLAY` variable: `docker run --rm --env DISPLAY=$DISPLAY --volume /tmp/.X11-unix:/tmp/.X11-unix cavo789/xeyes` — you'll get the window shown above.

## Creating our own Firefox Docker image

So, once you've understood the very basic example of xeyes, you can think out-of-the-box: which GUI can I install using Docker?

Let's try Firefox... By using my favorite search engine, I've found this post: [Install Official Firefox .deb in Dockerfile](https://jetthoughts.com/blog/install-official-firefox-deb-in-dockerfile-docker-devops/).

In a Dockerfile and with small changes, this gives us this:

**Dockerfile**

```docker
# cspell:disable

FROM ubuntu:latest

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
    apt-get install --no-install-recommends -y apt-utils ca-certificates curl gpg && \
    rm -rf /var/lib/apt/lists/*

# https://jetthoughts.com/blog/install-official-firefox-deb-in-dockerfile-docker-devops/
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    --mount=type=tmpfs,target=/var/log \
    # Create a directory to store APT repository keys, repository lists, and preferences if they don't exist
    install -d -m 0755 /etc/apt/keyrings /etc/apt/preferences.d /etc/apt/sources.list.d > /dev/null && \
    # Import the Mozilla APT repository signing key
    curl -fsSL https://packages.mozilla.org/apt/repo-signing-key.gpg |  \
    gpg --dearmor --no-tty -o /etc/apt/keyrings/packages.mozilla.org.gpg > /dev/null && \
    # Add the Mozilla APT repository to the APT sources list
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/packages.mozilla.org.gpg] https://packages.mozilla.org/apt mozilla main" |  \
    tee /etc/apt/sources.list.d/packages.mozilla.org.list > /dev/null && \
    # Configure APT to prioritize packages from the Mozilla repository
    echo "Package: *\nPin: origin packages.mozilla.org\nPin-Priority: 1000\n\n" | tee /etc/apt/preferences.d/mozilla > /dev/null && \
    # Update your package list and install the Firefox .deb package
    apt-get update -qq > /dev/null && \
    DEBIAN_FRONTEND=noninteractive apt-get install -qq --no-install-recommends firefox > /dev/null && \
    rm -rf /var/lib/apt/lists/*

CMD [ "firefox" ]
```

To build the image, please run the next command (and remember to replace `cavo789` with your username): `docker build --tag cavo789/firefox .`.

And to start Firefox, just run `docker run --rm -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY cavo789/firefox`.

![Running Firefox in a window](./images/firefox.webp)

As you know, my OS is Windows 11 and I'm running Linux thanks to the amazing WSL2 technology. So, in short, here above, you can see I've started Firefox for Debian as a windowed application in my Windows.

The old MS-DOS developer in me continues to be amazed by this possibility.

## Creating our own Chrome Docker image

We can do the same with Chrome:

**Dockerfile**

```docker
FROM ubuntu:latest

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    && apt-get install -y --no-install-recommends wget \
    && wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
    && apt-get install -y ./google-chrome-stable_current_amd64.deb \
    && rm -rf /var/lib/apt/lists/*

CMD ["google-chrome", "--disable-dev-shm-usage", "--disable-gpu"]
```

Build the image using `docker build --tag cavo789/chrome .` then run it using `docker run --rm -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY cavo789/chrome`.

![Running Chrome in a window](./images/chrome.webp)

## Creating our own GIMP Docker image

Ok, now, I think you've understood how it works. So, very shortly, here is how to run GIMP for Linux in a Docker container:

**Dockerfile**

```docker
FROM ubuntu:latest

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install --no-install-recommends -y gimp && rm -rf /var/lib/apt/lists/*

CMD [ "gimp" ]
```

Create the image by running `docker build --tag cavo789/gimp .`.

And to start GIMP, just run `docker run --rm -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY cavo789/gimp`.

![Running GIMP for Linux in a Docker container](./images/gimp.webp)

## Conclusion

Same recipe every time — share `DISPLAY`, mount `/tmp/.X11-unix`, `docker run` — and any Linux GUI
app shows up as a native window on the host, whether it's a silly `xeyes` demo or a full browser
or image editor. If a single application isn't enough and you'd rather have a whole desktop to
play with, [Start lubuntu Desktop in Docker](/blog/docker-lubuntu) is the next
step.
