Tips for seamlessly using Cygwin and WSL in Windows 10

Robin Coe
5 min readJan 3, 2019

--

I like Windows 10…now that it includes support for native bash. But it’s not the most straightforward environment to use when it’s necessary to run native windows apps, like Docker.

Before I started developing applications that were intended for Docker, I could get away with setting up a Bash profile for WSL (Windows Bash) and a separate environment for Windows using Cygwin. The great thing about Cygwin is that it supports running native Windows executables, so as long as those executables don’t expect a file system mounted on Windows, i.e., C:\. It’s even possible to have a more-or-less seamless development environment using an IDE like Eclipse, Intellij or VS Code, and setting up an embedded TTY terminal instead of a DOS cmd shell. While there are limitations with this setup, from the cosmetic — running a maven build inside a Cygwin shell that is running inside Eclipse will output control characters that are invisible when using Cygwin from its own shell; to the impossible — launching an application as if it’s running in a native Bash shell, it’s an acceptable compromise when you’re stuck developing in Windows. But now that Windows 10 includes WSL, the challenge is to make a truly seamless development environment that allows developing in either OS. And it’s not a small challenge, because low-level file system and socket connectors are still owned by Windows and only emulated by the Bash environment. This makes certain actions quite tricky, like using Docker inside WSL. So, my goal was to create a Windows environment that allowed me to use either Cygwin or WSL, without worrying about which shell I was operating in.

Problem is, the two environments do not share the same file system mount point nor (likely) the same $PATH:

Cygwin TTY (left) vs Windows Bash (right)

While running Windows executables is possible in recent Windows Insider builds, release builds don’t yet allow that. Fortunately for me, I’ve been a part of the Windows Insider program since October 2014 and can report good results on running native Windows binaries inside WSL. But as of this writing, building and running a Docker image in Bash still requires installing a Linux variant of Docker. I say Linux and not Ubuntu, because Insider builds allow several Linux distros. And while it’s possible to build and run Docker images in WSL, you still need to connect to a native Windows Docker daemon. And as of very recently, the Docker Daemon must use Hyper-v.

By the way, a very good explanation of setting up Docker in WSL is available here.

Given the predicate that sharing the same Bash behaviour is good but maintaining two copies of identical settings is not, I endeavored to isolate what I needed and operate against a shared environment; as any good developer knows, maintenance headaches come from code duplication, so having two copies of the files that control shell behaviour is problematic. I also wanted to develop a pattern for future work; knowing ahead of time where to install executables or set environment variables makes developing a CI/CD pipeline simpler. It also makes it possible to remove variables related to operating system or environment nuance when debugging.

To set a shared environment requires making those shared files relative to the bash environment I work in. For two reasons I chose to make Cygwin my home directory for Bash;

  1. I have been using Cygwin for over 15 years and had an environment that I know works in Windows.
  2. I learned the hard way that being a Windows Insider means taking on risk; a recent build stopped WSL from working and my Bash environment was completely inaccessible for a couple of weeks. I ended up losing the entire file system, including my Bash environment. In essence, I suffered a drive failure.

So, to create a shared environment, I abstracted the home directory from both Cygwin and Bash. In Cygwin, I created a symlink to my Cygwin home directory:

$ ln -s /cygdrive/d/cygwin64/home/rcoe cyghome

Note: Cygwin uses a magic cookie to identify symlinks. To have Cygwin create symlinks that can be followed from WSL, create an environment variable CYGWIN and set the value to winsymlinks:native. See https://cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks

and in Bash:

$ ln -s /d/cygwin64/home/rcoe cyghome

Note that I had changed my mount point for WSL to / instead of /mnt/

The next step was to use cyghome instead of $HOME or ~ in my .bash_aliases file:

export CATALINA_HOME=~/cyghome/apache-tomcat-9.0.4/
export M2_HOME=~/cyghome/apache-maven-3.5.2/
export DOCKER_HOST=tcp://localhost:2375

alias 2linked="cd ~/cyghome/git/linkEd"
alias 2chat="cd ~/cyghome/git/linkEd/chatsrv"
alias 2web="cd ~/cyghome/git/linkEd/webapp"
alias minishift="~/cyghome/minishift-1.29.0-windows-amd64/minishift.exe"

PATH=~/cyghome/bin:$PATH:$CATALINA_HOME/bin:~/cyghome/vertx/bin:$M2_HOME/bin:

# Default to human readable figures
alias df='df -h'
alias du='du -h'
#
# Misc :)
# alias less='less -r' # raw control characters
# alias whence='type -a' # where, of a sort
alias grep='grep --color' # show differences in colour
alias egrep='egrep --color=auto' # show differences in colour
alias fgrep='fgrep --color=auto' # show differences in colour
#
# Some shortcuts for different directory listings
alias ls='ls -hF --color=tty' # classify files in colour
alias dir='ls --color=auto --format=vertical'
alias vdir='ls --color=auto --format=long'
alias ll='ls -l' # long list
alias la='ls -la' # all but . and ..
alias l='ls -CF' #

I use .bash_aliases instead of .bashrc, because I use the appropriate .bashrc for the environment, which gets built from the skeleton file for the distribution. Because most .bashrc files get built with a condition to source a .bash_aliases file, all I need is a light-weight change to uncomment the test that sources the file into the bash environment:

if [ -f "${HOME}/.bash_aliases" ]; then
source "${HOME}/.bash_aliases"
fi

The last step is to create symlinks in WSL Bash where necessary:

rcoe@SKELETOR:~$ ls -la ~ | grep "\->"
lrwxrwxrwx 1 rcoe rcoe 21 Jan 3 16:43 .bash_aliases -> cyghome/.bash_aliases*
lrwxrwxrwx 1 rcoe rcoe 21 Nov 18 18:30 .bash_history -> cyghome/.bash_history
lrwxrwxrwx 1 rcoe rcoe 26 Dec 20 15:26 .dircolors -> /d/cygwin64/etc/DIR_COLORS
lrwxrwxrwx 1 rcoe rcoe 16 Nov 18 20:25 .inputrc -> cyghome/.inputrc*
lrwxrwxrwx 1 rcoe rcoe 12 Jan 3 16:47 .vim -> cyghome/.vim
lrwxrwxrwx 1 rcoe rcoe 14 Nov 18 20:12 .vimrc -> cyghome/.vimrc*
lrwxrwxrwx 1 rcoe rcoe 21 Dec 20 15:26 cyghome -> /d/cygwin64/home/rcoe/

Note that I include a symlink to the same .dircolors that cygwin uses, which is not relative to cyghome. This gets me very close to the same L&F regardless of shell.

Obviously, this setup needs to be maintained, because there’s no built-in synchronization between cygwin and Bash. So, if I run $ mvn install in Bash, I will end up with a local .m2/repositories folder with my dependencies. It would be preferable to set up my settings.xml with a repository location element to avoid that but, unfortunately, Maven on Windows does not honour that setting. So that means I have a local Bash ~/.m2/ directory as well as a $USERPROFILE/.m2/ directory for cygwin. As with all things code, one must adapt to nuance. Especially when operating systems are in-play.

--

--

Robin Coe

Long term lurker, short term contributor. Decided it was time to document what I find interesting in software development, heavily weighted towards Java.