Running Linux GUI Apps in Docker on Mac

Why would you want to run Linux apps in Docker on a Mac? Maybe there's a Linux program you love or you want the added security of sandboxing an app. Or maybe, as was the case with me, you're developing a C++ program targeting Linux and don't want to cross compile your dependencies.

The following steps get the CLion IDE running in a Docker container created during the project's build process. It uses XQuartz, a version of the X Window system that runs on macOS, along with socat.

  1. Download the Linux version of CLion
  2. Each time dependencies change, run to create a Docker image and container. The script assumes ~/dev is your code directory. If you use something different, edit the script or run it as CODE_DIR=<your_dir> ./
  3. Run to start the container and exec bash inside of it
  4. From inside the container, run /home/dev/clion-XXX/bin/ to start CLion. If this fails, ensure your container contains a JRE by either manually running apt install default-jre or adding it to the Dockerfile.
  5. Either select "Evaluate for free" or enter your activation code
  6. Open your project
  7. Build it and enjoy code completion, etc.!
  8. If you make changes to settings they'll be lost each time you create a new container. To save them, run docker cp my_project:/root/.CLionXXX/ ~/dev and import them next time you start CLion in a fresh container.
EXISTING_IMAGE_ID=$(docker ps --all --filter "name=$NAME" --format "{{.Image}}")

if [[ -n "$EXISTING_IMAGE_ID" ]]; then
  read -p "$NAME already exists. Delete it? " answer

  while true
    case $answer in
    [yY]* ) docker stop $NAME > /dev/null 2>&1
            docker rm $NAME > /dev/null 2>&1
            docker rmi $EXISTING_IMAGE_ID
    * )     echo "exiting"
  echo "$NAME does not exist, creating..."

# Build your docker container

IP_ADDRESS=$(ifconfig en0 | grep "inet " | cut -d " " -f 2)
IMAGE_ID=$(docker images --format "{{.ID}}" build/local/my-project:latest)

echo "Creating container using image ID $IMAGE_ID, mapping IP address $IP_ADDRESS, and mounting $CODE_DIR..."
docker create -it -e DISPLAY=$IP_ADDRESS:0 -v $CODE_DIR:/home/dev/ $IMAGE_ID bash

GENERATED_NAME=$(docker ps --all --filter "ancestor=$IMAGE_ID" --format "{{.Names}}")
echo "Renaming $GENERATED_NAME to $NAME..."
docker rename $GENERATED_NAME $NAME

echo "\nContainer created, run ./ to exec bash inside of it."

brew ls --versions $APP > /dev/null 2>&1 || brew install $APP
brew cask ls --versions $APP > /dev/null 2>&1 || brew cask install $APP

if [ "$(lsof -nP -i4TCP:6000 | grep -c LISTEN)" -eq 0 ]; then
  socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\"

open -a Xquartz
docker start $NAME > /dev/null 2>&1
docker exec -it $NAME bash

Stay up to date

Get notified when I publish. Unsubscribe at any time.