Remote Docker Container with GUI support in VSCode

A guide to setup a remote Docker container with GUI support in VSCode.

  ·   5 min read


There are probably already over a 100 blogs writing about this topic. Still, when I tried create the following setup, I had a hard time finding all the necessary information in one place. So I decided to write my own. I hope this helps you as well.

Setup

Let me start with a description of my setup, and what I tried to solve. I would like to run my code remotely on a Linux machine, and login using SSH from my Windows machine. Furthermore, I would like this workflow to integrate with VSCode as my IDE. I want to be able to run my code in a Docker container, and have the GUI of the container show up on my Windows machine. Lastly, I want to be a non-root user operating from within the Docker container. As I said, most components in this process are already well documented. However, it is the combination that turned out to be quite tricky.

Docker

First we need to create a minimal Dockerfile to create our image. I am sure you have your own needs, but in this tutorial I try to keep it as simple as possible.

Dockerfile
# base image
FROM ubuntu:22.04

ARG DEBIAN_FRONTEND=noninteractive

# install x11 utility apps for testing (xeyes)
RUN apt-get -y update && \
    apt-get -y upgrade && \
    apt update && apt install -y --no-install-recommends \
        sudo \
        x11-apps

# Add user and give root access
ARG UID=12345
ARG UNAME=dkr-12345
ARG GNAME=users
RUN useradd \
  --no-log-init \
  --create-home \
  --shell /bin/bash \
  --gid $GNAME \
  --uid $UID $UNAME
RUN chown -R $UNAME:$GNAME /home/$UNAME
RUN echo $UNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$UNAME
RUN chmod 0440 /etc/sudoers.d/$UNAME

# Set the default user. Omit if you want to keep the default as root.
USER $UNAME

As a start, pick an appropriate base image, which is irrelevant for this tutorial. Next do your installations, for this test, we only need the x11-apps package. Also, add your user, for instance 12345, and refer to it inside the Docker container as dkr-12345. This allows us to easily create files in the container as ourself, and not as root, which is generally the way to do it. It will prevent hassle with permissions. This user is added to its group and given a home directory. Finally, for ease of debugging inside the container we also give it root access (optional). Finally, we can run the following command to build the image on our remote Linux machine.

shell
docker build my_image_name .

Remote connection with X11

Next, in MobaXterm on Windows, we can create a new session and connect to our remote machine. We then run the following command docker run to start the container. Since we need to add many flags to the command, I decided to create a bash script to run the container. This script is shown below.

start_container.sh
#!/bin/bash
set -e
HOME_DIR=/home/$(id -u)
WORK_DIR=$HOME_DIR/my_project_dir
DOCKER_NAME="my_container_name"

XSOCK=/tmp/.X11-unix
XAUTH=/home/$(id -u)/.Xauthority
XAUTHDKR=/home/dkr-$(id -u)/.Xauthority

docker run -it --rm \
    --gpus all \
    --user=$(id -u):$(id -g) \
    --volume="$HOME_DIR:$HOME_DIR" \
    --volume="$XSOCK:$XSOCK:ro" \
    --volume="$XAUTH:$XAUTHDKR:rw" \
    --net=host \
    --env "DISPLAY=$DISPLAY" \
    --workdir "$WORK_DIR" \
    --name "$DOCKER_NAME" \
    --detach \
my_image_name

There is a lot, so let’s break this down. To enable the graphical stuff to be ported over the remote connection, we need something called X11. X11 is a remote-display protocol used by Linux/Unix machines. To be able to make use of X11 on Windows, we need to run an X11 server locally. This is not native to Windows. Luckily, MobaXTerm already has X11 support build in, so we need to SSH into our remote machine with X11 forwarding enabled. This can be done by adding the -Y flag to the SSH command. However, to be able to use X11 inside the Docker container, we need link some files. You can see we made some variables XSOCK and XAUTH, which we mount as a volume in our container. Also, we need the --net=host option to be set. Make sure these files are correctly mounted to your new user (dkr-12345) directory in the container.

After that we run start_container.sh to start the docker container. The whole process is executed using the following two commands.

MobaXTerm shell
ssh -Y user@remote_machine_ip
./start_container.sh

You should already be able to test out if the X11 forwarding is working properly. You can do this by running xeyes in the MobaXTerm shell. This is the reason why we installed the x11-apps dependency, which includes this little app. If you see two eyes following your mouse, then you are good to go. Congratulations! You have successfully created a remote Docker container with GUI support.

Integration with VSCode

After that, we can dettach the MobaXTerm shell from the container to not mess up (read escape sequence Ctr+P Ctr+Q). Or simply at the --detach flag to the docker run command. Then in VSCode simply attach to the container. You can easily do that using the Docker extension for VSCode. Unfortunately, the display variable is not set correctly in the VSCode shell, sometimes?. You would have to manually (or find a different solution, which I’m sure there is) set the display variable. To do this, check to which display MobaXTerm is porting using

MobaXTerm shell
echo $DISPLAY
>> localhost:10.0

Then set the display variable in VSCode after attaching to the container.

VSCode terminal
echo $DISPLAY
>> host.docker.internal:0.0
export DISPLAY=localhost:10.0

Summary

  • Setup connection from Windows client to Linux remote.
  • Build a Docker container that supports X11 forwarding as non-root user.
  • Run GUI apps from VSCode.
MobaXTerm shell
# SSH into remote linux
ssh -Y user@remote_machine_ip
# go to project directory where Dockerfile is located
cd $home/my_project_dir
# build Docker image
docker build my_image_name .
# start container
./start_container.sh

Finally, attach to the container and set the display variable in VSCode and start running your GUI apps!