Notes:

  • Docker For Windows is newer and officially recommended over Docker Toolbox, but it doesn’t run on Windows Home edition (only Pro) because it requires Hyper-V. The following instructions may or may not apply to Docker For Windows (I haven’t tested it).
  • This works on Azure type Dv3 and Ev3 VMs (Windows 2016), as well as regular Windows 10 desktop. I used this Terraform script to provision a VM for testing.

Setup

Install Cygwin

Download and install Cygwin (64-bit), including the openssh and wget packages (and optionally others such as vim).

Install Docker Toolbox

Download and install Docker Toolbox, including Docker Compose and VirtualBox (if not already installed). Kitematic and Git for Windows are not required.

Install WinPTY

Cygwin doesn’t play well with interactive Windows console programs, so WinPTY is required to bridge the gap. To install it, run this in Cygwin:

cd /tmp
wget https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-cygwin-2.8.0-x64.tar.gz
tar zxf winpty-0.4.3-cygwin-2.8.0-x64.tar.gz
cp -r winpty-0.4.3-cygwin-2.8.0-x64/* /usr
rm -rf winpty*

Initialise the Docker VM

In Cygwin:

# Create a Docker VM and configure the shared folder inside the VM to match the paths in Cygwin, which makes using Docker much easier
# If your project files are on a drive other than c:\ change this accordingly
docker-machine create --driver virtualbox --virtualbox-share-folder "c:\:/cygdrive/c" default

# Symlink /c/ to /cygdrive/c/ in the Docker VM so Docker Compose works correctly
docker-machine ssh default "sudo ln -nsf /cygdrive/c /c"

Initialise the environment variables

eval "$(docker-machine env)"

Using Docker

Start the VM and initialise the environment variables

In future sessions, run the following commands (or add them to ~/.bashrc to run them automatically):

docker-machine start
docker-machine ssh default "sudo ln -nsf /cygdrive/c /c"
eval "$(docker-machine env)"

(Note: The symlink seems to be removed whenever the VM is restarted, so it must be recreated. I’m not sure why or if there’s a way to prevent it.)

WinPTY

Whenever you run a Docker command, especially an interactive one, prefix it with winpty:

winpty docker run -it --rm ubuntu
winpty docker-compose exec wordpress bash

To make it easier, you can add these aliases to ~/.bashrc to automatically add the prefix:

alias docker='winpty docker'
alias docker-compose='winpty docker-compose'

However, if you get the following error, you may need to drop the winpty prefix (or use command docker/command docker-compose to bypass the alias) until this issue in Windows 10 is fixed:

Traceback (most recent call last):
  File "docker-compose", line 3, in <module>
  File "compose\cli\main.py", line 68, in main
  File "compose\cli\main.py", line 118, in perform_command
  File "compose\cli\main.py", line 926, in up
  File "compose\project.py", line 401, in up
  File "compose\service.py", line 305, in ensure_image_exists
  File "compose\service.py", line 1001, in pull
  File "compose\progress_stream.py", line 37, in stream_output
  File "codecs.py", line 370, in write
  File "site-packages\colorama\ansitowin32.py", line 40, in write
  File "site-packages\colorama\ansitowin32.py", line 141, in write
  File "site-packages\colorama\ansitowin32.py", line 169, in write_and_convert
  File "site-packages\colorama\ansitowin32.py", line 174, in write_plain_text
IOError: [Errno 0] Error
Failed to execute script docker-compose

Sharing files with Docker

Because we mapped both the Cygwin (/cygdrive/c/) and Windows (/c/) style paths into the Docker VM, you can share files as normal in both Docker:

winpty docker run -it --rm -v $PWD:/shared ubuntu ls -l /shared

And Docker Compose:

# docker-compose.yml
version: '3.3'
services:
  ubuntu:
    image: ubuntu:latest
    volumes:
      - ./:/shared
winpty docker-compose run ubuntu ls -l /shared

As long as the directories are on the drive shared earlier (e.g. c:\).

Accessing Docker services

To get the IP address of the Docker VM, run:

docker-machine ip

Then use this IP to access the service (instead of localhost) – e.g. http://192.168.99.100:8000/.