tl;dr the command you want is cmd //c "SET pwd=${PWD} & docker run --rm -it -v %pwd%:/usr/src/pwd python:3 bash

If you use Git BASH1 and Docker2, you may have tried to mount a directory from your host into a Docker container. This morning, I wanted to mount my current working directory into a Python3 container and get an interactive shell. The command I ran in Git BASH:

docker run -it -v ${PWD}:/usr/src/pwd python:3 bash

the error I received:

C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: Mount denied:
The source path "C:/code/sandbox;C"
doesn't exist and is not known to Docker.
See 'C:\Program Files\Docker\Docker\Resources\bin\docker.exe run --help'.

Huh? What’s causing this error? Why does Docker think my source path is C:/code/sandbox;C? Why does the path end in ;C?

Simplify the command

To start, let’s simplify our command. Right now the command contains the ${PWD} syntax. This syntax is known as parameter expansion[^4] (more generally, this concept is known as string interpolation4. When our command is evaluated, the ${PWD} symbol will be replaced with the result of evaluating PWD5, or print working directory.

In my case, PWD evaluates to /c/code/sandbox, so the command becomes docker run -it -v /c/code/sandbox:/usr/src/pwd python:3 bash.

Why is Docker trying to mount C:/code/sandbox;C when the value of PWD is /c/code/sandbox? To understand that, we need to dig into something called POSIX path conversion.

POSIX path conversion

Git BASH implements POSIX6, which is a set of standards that some operating systems implement. Windows, by contrast, implements the Windows API7. Since Git BASH is “speaking POSIX”, and our Windows host is “speaking Windows API”, and Docker for Windows is also speaking the Windows API, a translation is needed in order to handle communication between POSIX and Windows API boundaries. Specifically, the file path /c/code/sandbox is not a valid Windows path 8, so something needs to convert it – Git BASH handles this conversion for us.

When we execute our command, Git BASH interrogates the command string for any POSIX file paths that are used as arguments. If Git BASH finds one, it attempts to convert9 those file paths to Windows API file paths following the POSIX path conversion rules10. If an argument starts with a / immediately followed by a drive specifier (such as c), that argument is considered a POSIX file path and it’ll be converted.

Our -v11 argument, /c/code/sandbox:/usr/src/pwd, meets this criteria (because it starts with /c), so it will be converted.

The root of our problem is the fact that our -v argument contains a colon (:). Per the POSIX path conversion rules12, any file path that contains a colon is treated as a POSIX path variable13, which is translated into a Windows API path variable. A POSIX path variable is delimited using a colon, a Windows API path variable is delimited using a semicolon. So, the conversion dictates that all colons are replaced with semicolons, and herein lies the source of the malformed path that Docker is getting.

To see this in action, you can run the following command in Git BASH: cmd //c echo /c/code/sandbox:/usr/src. This command is invoking the Windows cmd.exe program14, with the following arguments:

  1. //c tells cmd.exe to run the command and immediately exit afterwards. The reason we have two forward slashes is the POSIX path conversion – a / followed by a drive specifier is considered a path, but two slashes escapes it.
  2. echo15 is a command that prints whatever value you give it.
  3. /c/code/sandbox:/usr/src is the value to be echoed. This particular value emulates our Docker -v argument and will be subject to the POSIX path translation.

The result of the command should be something like C:\code\sandbox;C:\Program Files\Git\usr\src.

Finally, I’m assuming that Docker is splitting the -v argument on :, meaning that it would read the source portion of the [source]:[container] argument as C:\code\sandbox;C.

Now that we understand the problem, let’s explore a few solutions.

Disable the path conversion

One approach is to just disable the POSIX path conversion by using the MSYS_NO_PATHCONV flag16. You can disable it at the system level17, or you can disable it at the command level18.

As an example, to disable it at the system level you need to prepend MSYS_NO_PATHCONV=1 to your command: MSYS_NO_PATHCONV=1 docker run --rm -it -v ${PWD}:/usr/src/pwd python:3 bash.

Let Windows do the current directory resolution

Another approach is to avoid the POSIX translation altogether by passing the whole command to the Windows shell:

cmd //c "SET pwd=${PWD} & docker run --rm -it -v %pwd%:/usr/src/pwd python:3 bash"

Wrapping up

Whichever solution you use, I’d recommend adding it to your .bashrc file. I decided to go with the second solution, so I have the following line in my .bashrc file: alias dh='cmd //c docker run --rm -it -v %cd%:/usr/src/pwd'. If I want to start a Python container and have my current directory mounted, I simply type dh python:3 bash.