I’m also a fan of azbrowse for working with Azure resources from the terminal, and lately have found myself running azbrowse from within a dev container for various reasons.
There are several features in azbrowse that copy data to the clipboard, and when run from WSL it detects that and copies to the Windows clipboard, which is convenient. When run from a dev container, the experience isn’t so good (a polite way of saying that it doesn’t work).
In this post I’ll take a tour through the setup I am using to enable applications running in dev containers to copy content onto my Windows clipboard. If you just want the final code then skip to the end, otherwise keep reading…
Before we dive in, this post assumes that you have:
- WSL 2
- Docker Desktop for Windows configured for WSL 2 integration
- Visual Studio Code (VS Code)
- VS Code Remote-Containers extension
Windows has shipped a utility called clip.exe for a long time. It’s a handy little utility that reads from stdin and copies the contents to the clipboard. For example, running the following snippet in PowerShell will copy
Hello to your clipboard:
echo "Hello" | clip.exe
Even better, WSL 2 (by default) allows us to call Windows apps from within WSL. So, running the following snippet in bash will copy
Hello from bash to your Windows clipboard from inside WSL:
echo "Hello from bash" | clip.exe
Now that we have a way to write to the clipboard, we need a way to invoke it from a dev container. The first part of this is to have something listening in WSL and forwarding on to
clip.exe. For this, we can use
sudo apt install socat if you don’t have it installed). There are many options to choose from with
socat, but here we will instruct it to listen on a TCP port and execute
clip.exe passing the incoming traffic. Run the following in bash:
socat tcp-listen:8121,fork,bind=0.0.0.0 EXEC:'clip.exe'
In this example,
socat is listening on port
8121 (I have no real reason for picking this port - change it as you see fit!).
socat listening on a TCP port, the next step is to send it some text via TCP and we can make use of
socat again. Run the following command in another bash window:
echo "hello via socat" | socat - tcp:localhost:8121
This command pipes
hello via scoat into the
socat command to send it via TCP. The previous
socat command is listening and forwards the text to
clip.exe resulting in
hello via socat being placed onto your Windows clipboard.
Since the goal is to enable copying to the clipboard from a VS Code dev container, the next step is to test in a container. With the
socat listener still running, Run the following command to launch an ubuntu container:
docker run --rm -it ubuntu bash
socat in the container and run a slight variation of the
socat command to send a value to the listener:
apt-get update && apt install -y socat echo "hello from a container" | socat - tcp:host.docker.internal:8121
In this example, we use the host.docker.internal address in the container, which is a special domain name that Docker Desktop for Windows sets up, and which maps to the IP address for the host. This provides us a way to communicate back to the
socat listener we have running, and after running the last snippet in the docker container you will have
hello from a container on your Windows clipboard!
As cool as it is to be able to send data from a container to the Windows clipboard with a custom command, the goal is to redirect content that applications in a container write to the clipboard so that it ends up on the Windows clipboard.
For this step I took inspiration from this repo which fakes out
xclip - commonly used utilities for putting text on the clipboard.
To set up the content for this section, run the following in the container:
mkdir /cliptest cd /cliptest echo -e "for i in \"\$@\"\ndo\n case \"\$i\" in\n (-i|--input|-in)\n tee <&0 | socat - tcp:host.docker.internal:8121\n exit 0\n ;;\n esac\ndone" > clip.sh chmod +x clip.sh ln -s clip.sh xsel ln -s clip.sh xclip export PATH=/cliptest:$PATH
The above snippet creates
/cliptest/clip.sh with the following content (and makes it executable):
for i in "$@" do case "$i" in (-i|--input|-in) tee <&0 | socat - tcp:host.docker.internal:8121 exit 0 ;; esac done
When this script is run, it checks for the
-in flags that are used with
xclip to specify input to copy to the clipboard. If it finds them then it copies the input to
socat as in our previous example (to send to the
You can test that this is working by running the following in the container:
echo "hello from clip.sh" | ./clip.sh -i
The earlier snippet that created
clip.sh also created symbolic links to it named
xclip. It also added the folder that these symlinks are in to the
PATH, so that running the following command will copy
hello from fake xsel to the Windows clipboard from inside the container.
echo "hello from fake xsel" | xsel --input
With the fake implementation in place, any process using
xclip to copy content to the clipboard from within the container will actually be sending it via our
socat relay to the Windows clipboard!
Now all that remains is to make sure that any dev containers you run have the fake
There’s a handy feature in dev containers that supports dotfiles repositories. With this dotfiles support, you can configure VS Code so that whenever it builds a dev container, it clones your repository inside it and runs a script from it.
We can use this to have a repository with the
clip.sh script and symbolic links, and scripts to add update the
So, to put all this together we need to do two things:
- update the host to launch the
- configure VS Code to clone and use the dotfiles repo
To set up the
socat listener in WSL, add the following to your
if [[ $(command -v socat > /dev/null; echo $?) == 0 ]]; then # Start up the socat forwarder to clip.exe ALREADY_RUNNING=$(ps -auxww | grep -q "[l]isten:8121"; echo $?) if [[ $ALREADY_RUNNING != "0" ]]; then echo "Starting clipboard relay..." (setsid socat tcp-listen:8121,fork,bind=0.0.0.0 EXEC:'clip.exe' &) > /dev/null 2>&1 else echo "Clipboard relay already running" fi fi
This snippet has some additional checks and configuration compared to the earlier example (such as using
setsid). For details on this, see the post on Forwarding SSH Agent requests from WSL to Windows which covers the same steps for ensuring that the listener continues running etc.
To automatically add the clipboard support when a dev container is built, configure the dotfiles repositories to be https://github.com/stuartleeks/wsl-dev-container-clipboard-dotfiles (or add the example code there to your own dotfiles repository).
If you are configuring via the VS Code “Prefernces: Open Settings (JSON)” command, the following configuration options will configure the dotfiles using the example repo:
"remote.containers.dotfiles.installCommand": "~/dotfiles/install.sh", "remote.containers.dotfiles.repository": "https://github.com/stuartleeks/wsl-dev-container-clipboard-dotfiles", "remote.containers.dotfiles.targetPath": "~/dotfiles",
That’s all for this time! I hope you found it useful and/or interesting, whether as a solution or as a starting point. This solution works well for me, but take it and customise it to suit you. Or just take the techniques and use them for something altogether different 😃.
P.S. If you liked this, you may also like my book “WSL 2: Tips, Tricks and Techniques” which covers tips for working with WSL 2, Windows Terminal, VS Code dev containers and more https://wsl.tips/book :-)