530

I am trying to dockerize a PHP application. In the dockerfile, I download the archive, extract it, etc.

Everything works fine. However, if a new version gets released and I update the dockerfile, I have to reinstall the application, because the config.php gets overwritten.

So I thought I can mount the file as a volume, like I do with the database.

I tried it two ways, with a volume and a direct path.

docker-compose:

version: '2'
services:
  app:
    build: src
    ports:
      - "8080:80"
    depends_on:
      - mysql
    volumes:
      -  app-conf:/var/www/html/upload
      -  app-conf:/var/www/html/config.php
    environment:
      DB_TYPE: mysql
      DB_MANAGER: MysqlManager

  mysql:
    image: mysql:5.6
    container_name: mysql
    volumes:
      - mysqldata:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD:
      MYSQL_DATABASE:
      MYSQL_USER:
      MYSQL_PASSWORD:

volumes:
  mysqldata:
  app-conf:

Which results in the error:

And I tried it with a given path, as a mounted volume.

/src/docker/myapp/upload:/var/www/html/upload
/src/docker/myapp/upload:/var/www/html/config.php

However, both ways are not working. With the mounted volume, I see that upload gets created.

But then it fails with:

/var/www/html/config.php\" caused \"not a directory\"""

If I try it with

/src/docker/myapp/upload/config.php:/var/www/html/config.php

Docker creates the upload folder and then a config.php folder. Not a file.

Or is there another way to persist the config?

Pang
  • 9,564
  • 146
  • 81
  • 122
Jakub Juszczak
  • 7,126
  • 3
  • 21
  • 38

21 Answers21

678

TL;DR/Notice:

If you experience a directory being created in place of the file you are trying to mount, you have probably failed to supply a valid and absolute path. This is a common mistake with a silent and confusing failure mode.

File volumes are done this way in docker (absolute path example (can use env variables), and you need to mention the file name) :

    volumes:
      - /src/docker/myapp/upload:/var/www/html/upload
      - /src/docker/myapp/upload/config.php:/var/www/html/config.php

You can also do:

    volumes:
      - ${PWD}/upload:/var/www/html/upload
      - ${PWD}/upload/config.php:/var/www/html/config.php

If you fire the docker-compose from /src/docker/myapp folder

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
BlackStork
  • 7,603
  • 1
  • 16
  • 18
  • 163
    Like I said, if I try /src/docker/myapp/upload/config.php:/var/www/html/config.php Docker creates the upload folder and then a config.php folder. Not a file. – Jakub Juszczak Feb 16 '17 at 10:05
  • 15
    In your post you wrote differently. If the file is there it should mount as file. – BlackStork Feb 16 '17 at 17:27
  • https://docs.docker.com/engine/tutorials/dockervolumes/#/mount-a-host-file-as-a-data-volume <- should be defenitely working – BlackStork Feb 16 '17 at 17:36
  • 27
    In order for this to work I had to supply an absolute path. The tip using PWD helped a lot here, thanks! – Steve Smith Aug 07 '17 at 16:52
  • 5
    For me is creating a folder rather than the files, but I think because that file is copied later in that path is there anyway to postpone the binding? – eKelvin Oct 12 '17 at 08:42
  • 39
    if /src/docker/myapp/upload/config.php doesn't exists in the host docker will create a folder... What i'm doing is creating a script that creates an empty file before running docker run - would be ideal if I can instruct docker to do it for me via cmd line args. – cancerbero Nov 09 '17 at 22:14
  • Though it worked when the container already exist but in a fresh new instance when doing so I am getting the *ERROR: Duplicate mount points: issue* – Tara Prasad Gurung Apr 07 '19 at 06:01
  • 1
    My solution was even better - make sure the file you're trying to mount actually exists! If it doesnt, it will throw this error since it tries to create a directory with the given file name. – TetraDev Jan 30 '21 at 00:11
  • Windows users will want to call `set PWD=%CD%` before calling `docker-compose up` to populate the `PWD` variable. – Jthorpe Feb 18 '21 at 22:46
  • 1
    use `$(pwd)` instead of `${PWD}` on linux – codesmith May 25 '21 at 08:24
  • If it initially created a folder for you, make sure to stop and remove the problematic container so it can clean the folder and map the file from the host. – leoschet Jul 21 '21 at 12:51
  • If ${PWD} doesn't work. It might be you're using sudo. [Ref](https://stackoverflow.com/a/41948354/4123703) – Louis Go Jul 30 '21 at 07:04
  • my problem is that even after ensuring that the host file exists (and mirrors the container file) I'm still getting a "not a directory" error because it was created as directory once. there must be some way to reset this, because before the host file was deleted it was working exactly with this config. – br4nnigan Nov 02 '21 at 11:21
  • 4
    Just use relative paths ` - ./src/foo:/etc/foo` as PWD is undefined on windows and `-/` works on Linux and Windows. Paths are relative to the docker file. – Marc Dec 16 '21 at 07:02
  • 1
    Works fine with relative paths ` - ./php.custom.ini:/usr/local/etc/php/conf.d/php.custom.ini` – Firsh - justifiedgrid.com May 22 '22 at 11:24
  • 2
    How would the thing be easier if the docker would just refuse to start the container if the volume doesn't exist. With a helpful message. Wouldn't it? All this would be unnecessary and many directories owned by "root" under my user account would not be created. (E.g. for dev, mount "${PWD}/storage" into the container, but it didn't exist. Now it exists, owned by root, because docker creates it.) – Dalibor Filus Jun 14 '22 at 18:46
  • Here is an example why docker should not throw error when host path does exists: `docker run --rm --network none -v $(pwd)/a/b/c:/a/b/c -it alpine/make mkdir -p /a/b/c/d/e/f` it should create a dir (in host `pwd`) like `a/b/c/d/e/f`. You don't need to `mkdir -p a/b/c` before `docker run`. `a/b/c` is not created yet, so docker will not know it's a file or dir, or just not created yet, so it assumes it should be a dir (best guess, most of the time that's the case). – dvorak4tzx Dec 21 '22 at 12:03
144

I had been suffering from a similar issue. I was trying to import my config file to my container so that I can fix it every time I need without re-building the image.

I mean I thought the below command would map $(pwd)/config.py from Docker host to /root/app/config.py into the container as a file.

docker run -v $(pwd)/config.py:/root/app/config.py my_docker_image

However, it always created a directory named config.py, not a file.

while looking for clue, I found the reason(from here)

If you use -v or --volume to bind-mount a file or directory that does not yet exist on the Docker host, -v will create the endpoint for you. It is always created as a directory.

Therefore, it is always created as a directory because my docker host does not have $(pwd)/config.py.

Even if I create config.py in docker host. $(pwd)/config.py just overwirte /root/app/config.py not exporting /root/app/config.py.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
SangminKim
  • 8,358
  • 14
  • 69
  • 125
  • 31
    I just added a `touch path/to/file` in the `Dockerfile`so that when I mount it would still be a file (not a created dir) – Shadi Jan 31 '18 at 07:03
  • 11
    left side of `:` has to be full path. It works this way –  Jun 14 '18 at 09:09
  • 2
    The `touch` was the answer, do that in Dockerfile then your docker-compose volume mount will work correctly without creating a directory – ctrlbrk Jun 18 '20 at 00:08
  • And don't forget to `mkdir -p /path/to/file` for good measure before `touch /path/to/file` – Michael Jan 02 '21 at 15:42
  • 1
    Thanks! This was the issue for my case. I use './docker/uploads.ini:/usr/...' but the docker-compose file in in docker folder. So I change to './uploads.ini:/usr/...' and it works. – Dat TT Jun 12 '21 at 05:04
133

The way that worked for me is to use a bind mount

  version: "3.7"    
  services:
  app:
    image: app:latest
    volumes:
      - type: bind
        source: ./sourceFile.yaml
        target: /location/targetFile.yaml

Thanks mike breed for the answer over at: Mount single file from volume using docker-compose

You need to use the "long syntax" to express a bind mount using the volumes key: https://docs.docker.com/compose/compose-file/compose-file-v3/#long-syntax-3

Kamafeather
  • 8,663
  • 14
  • 69
  • 99
TetraDev
  • 16,074
  • 6
  • 60
  • 61
  • 4
    This answer is way more concise than the above and did exactly what I needed. Thank you. – Brodan Jul 28 '21 at 19:03
  • this doesn't work in case of file is created in container, it throws `Error response from daemon: invalid mount config for type "bind": bind source path does not exist` – Alexey Sh. May 18 '23 at 12:08
93

Use mount (--mount) instead volume (-v)

More info: https://docs.docker.com/storage/bind-mounts/

Example:

Ensure /tmp/a.txt exists on docker host

docker run -it --mount type=bind,source=/tmp/a.txt,target=/root/a.txt alpine sh
Joakim
  • 11,468
  • 9
  • 44
  • 50
Subbu M
  • 1,069
  • 7
  • 3
  • Thank you Subbu, @Jakub Juszczak this should be the accepted answer, it's the only answer that answers the question at all, and it worked for me – neoakris Oct 31 '19 at 07:17
  • 4
    This does not really answer the question. *volumes* will work as well (because the issue is in absolute path). The only difference between `--mount` and `-v` is behavior when host part of the volume doesn't exist yet. As per your link: >If you use -v or --volume to bind-mount a file or directory that does not yet exist on the Docker host, -v creates the endpoint for you. It is always created as a directory. – The Godfather Nov 08 '19 at 15:51
  • 6
    I do not know how to translate this into a docker-compose. 'Little help? – msanford Dec 12 '19 at 16:39
  • @msanford I added a docker-compose example to the answer. – sebisnow Mar 06 '20 at 07:48
40

As of docker-compose file version 3.2, you can specify a volume mount of type "bind" (instead of the default type "volume") that allows you to mount a single file into the container. Search for "bind mount" in the docker-compose volume docs: https://docs.docker.com/compose/compose-file/#volumes

In my case, I was trying to mount a single ".secrets" file into my application that contained secrets for local development and testing only. In production, my application fetches these secrets from AWS instead.

If I mounted this file as a volume using the shorthand syntax:

volumes:
 - ./.secrets:/data/app/.secrets

Docker would create a ".secrets" directory inside the container instead of mapping to the file outside of the container. My code would then raise an error like "IsADirectoryError: [Errno 21] Is a directory: '.secrets'".

I fixed this by using the long-hand syntax instead, specifying my secrets file using a read-only "bind" volume mount:

volumes:
 - type: bind
   source: ./.secrets
   target: /data/app/.secrets
   read_only: true

Now Docker correctly mounts my .secrets file into the container, creating a file inside the container instead of a directory.

Pang
  • 9,564
  • 146
  • 81
  • 122
Carlton Duffett
  • 471
  • 5
  • 4
  • I have the same error also by specifying a "bind" volume mount, any source is mounted as a directory – marco Feb 14 '23 at 21:07
30

For anyone using Windows container like me, know that you CANNOT bind or mount single files using windows container.

The following examples will fail when using Windows-based containers, as the destination of a volume or bind mount inside the container must be one of: a non-existing or empty directory; or a drive other than C:. Further, the source of a bind mount must be a local directory, not a file.

net use z: \\remotemachine\share

docker run -v z:\foo:c:\dest ...

docker run -v \\uncpath\to\directory:c:\dest ...

docker run -v c:\foo\somefile.txt:c:\dest ...

docker run -v c:\foo:c: ...

docker run -v c:\foo:c:\existing-directory-with-contents ...

It's hard to spot but it's there

Link to the Github issue regarding mapping files into Windows container

Community
  • 1
  • 1
Justin Lessard
  • 10,804
  • 5
  • 49
  • 61
  • 2
    Do you know if it is still the case in july 2020 ? – Ovidiu Buligan Jul 13 '20 at 07:40
  • 2
    @OvidiuBuligan Yes. According a comment from a docker developer on the issue, it's preferable to mount directory anyway, even on linux. They also said they cannot allow this behavior without a massive rewrite. So I really doubt we will see this in the short-medium term. – Justin Lessard Jul 13 '20 at 12:26
  • We faced the same scenario. But in K8s it allowed mounting as a destination file from conigMap entry. Link to repo in case anyone is interested. https://github.com/ms-azure-demos/aks-windows-container-configmap-file-mount-powershell – Joy George Kunjikkuru Nov 12 '22 at 18:44
30

All above answers are Correct.

but one thing that I found really helpful is that mounted file should exist inside docker host in advance otherwise docker will create a directory instead.

for example:

/a/file/inside/host/hostFile.txt:/a/file/inside/container/containerFile.txt

hostFile.txt should exist in advance. otherwise you will receive this error: containerFile.txt is a directory

TheEhsanSarshar
  • 2,677
  • 22
  • 41
18

You can also use a relative path in your docker-compose.yml file like this (tested on Windows host, Linux container):

volumes:
    - ./test.conf:/fluentd/etc/test.conf
Pang
  • 9,564
  • 146
  • 81
  • 122
Daniel
  • 984
  • 8
  • 14
  • 3
    I tested this on Mac with `version: '3.7'` specified in the `docker-compose.yml` file with docker-compose version 1.24.1, build 4667896b, and it worked. – Asclepius Oct 28 '19 at 19:29
  • 27
    I still get a directory instead of a file. – chovy Nov 22 '19 at 04:52
  • @chovy have a look on this Answer https://stackoverflow.com/a/66327349/10539792 – TheEhsanSarshar Feb 27 '21 at 17:19
  • And read that line over and over. I'm consistent in trying to map `.env` as `./env:/app.env` and forgetting the dot (so instead of `./.env:/app.env`). I think because I already typed it (to specify current dir) my muscle memory does not want to add the second dot.. :-P – Jos Jun 29 '23 at 15:38
4

In compose I am using a relative path and it works:

version: "3.7"
services:
    svc1: 
      volumes:
        # Current dir is parent of src
        - "./src/file.conf:/path/in/container/file.conf 

Using docker run command to bind mount a file produces:

docker: Error response from daemon: invalid mount config for type "bind": invalid mount path: 'path/file.conf' mount path must be absolute. 
See 'docker run --help'.

Apparently the only way to do this is to specify an absolute mount path like this:

docker run -it --rm --mount type=bind,source="/path/to/file.conf",target=/file.conf alpine sh

also using "%cd%" for Windows Users or "$(pwd)" for Linux Users is a way to deal with absolute paths.

see storage bind mounts

For Visual Studio Code Users make sure you are running the %cd% command in a Command Prompt Terminal, not PowerShell.

Affes Salem
  • 1,303
  • 10
  • 26
3

You can mount files or directories/folders it all depends on Source file or directory. And also you need to provide full path or if you are not sure you can use PWD. Here is a simple working example.

In this example, I am mounting env-commands file which already exists in my working directory

$ docker run  --rm -it -v ${PWD}/env-commands:/env-commands aravindgv/eosdt:1.0.5 /bin/bash -c "cat /env-commands"
Aravind GV
  • 41
  • 5
3

Be carefull if you use docker context to operate with docker on remote virtual machine. In this case the file you trying to mount will certanly not exist on target VM. Thus you not existed file will be mount as folder.

falko
  • 1,407
  • 14
  • 15
3

TL&DR: Docker defaults all mount points to directories. If you want to make sure your file is mounted into your docker instance, the file must already exist prior to docker-compose up and running.

Explanation:

Traditionally, the purpose of mounting a volume has always meant mounting a storage location to a directory. However, that is now changing with the ability to now just share files. From a programmer's perspective, its hard to know whether a "/tmp/something" is a file or a directory. Docker, therefore, assumes all mount points are first a directory unless the file already exists.

In your example if you want a file to be mounted, make sure it already exists on the host system. When Docker sees that this entry already exists it will mount the file into the docker environment.

FlyingV
  • 2,207
  • 19
  • 18
1

I had the same issue, docker-compose was creating a directory instead of a file, then crashing mid-way.

what i did :

  1. run the container without mapping the file

  2. copy the config file to the host location :

    docker cp containername:/var/www/html/config.php ./config.php

  3. remove the container (docker-compose down)

  4. put the mapping back and remount up the container

docker compose will find the config file, and will map that instead of trying to create a directory.

Chtioui Malek
  • 11,197
  • 1
  • 72
  • 69
1

2022 answer, on Mac with Minikube/Hyperkit docker and Docker Compose

Since I'm not using Docker Desktop any longer, I've experienced numerous issues similar to "docker in docker (dind)" paradigm with minikube...

  1. mount minikube
  2. use absolute path

e.g., easiest way was to mount the exact home path...

minikube mount $HOME:/Users/<you>

... keeps running...

docker-compose.yaml

volumes:
      - /Users/<you>/path/to/file.yaml:/somedir/file.yaml
Michael Ramos
  • 5,523
  • 8
  • 36
  • 51
0

For me, the issue was that I had a broken symbolic link on the file I was trying to mount into the container

jla
  • 1
0

I had the same issue on Windows, Docker 18.06.1-ce-win73 (19507).

Removing and re-adding the shared drive via the Docker settings panel and everything worked again.

Pool
  • 11,999
  • 14
  • 68
  • 78
0

In windows, if you need the a ${PWD} env variable in your docker-compose.yml you can creat a .env file in the same directory as your docker-compose.yml file then insert manualy the location of your folder.

CMD (pwd_var.bat) :

echo PWD=%cd% >> .env

Powershell (pwd_var.ps1) :

$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'; echo "PWD=$(get-location).path" >> .env

There is more good features hear for docker-compose .env variables: https://docs.docker.com/compose/reference/envvars/ especially for the COMPOSE_CONVERT_WINDOWS_PATHS env variable that allow docker compose to accept windows path with baskslash "\".

When you want to share a file on windows, the file must exist before sharing it with the container.

CallMarl
  • 524
  • 4
  • 15
0

Maybe this helps someone.

I had this problem and tried everything. Volume bindings looked well and even if I mounted directory (not files), I had the file names in the mounted directory correctly but mounted as dirs.

I tried to re-enable shared drives and Docker complained the firewall is active.

After disabling the firewall all was working fine.

Josef Sábl
  • 7,538
  • 9
  • 54
  • 66
  • Thank you for that! Your post made me realize that my selinux was set to enforcing and that was causing all of my troubles. Don't know why you were down-voted ¯\_(ツ)_/¯ – Shōgun8 Jan 17 '20 at 02:55
0

For myself, the issue was that I specified the incorrect source file.

Here's an example:

 robert ❱ ~ ❱ docker run --rm -it -v "${PWD}/non-existant:/root/destination" -w /root --entrypoint /usr/bin/ls ubuntu -lad destination
drwxr-xr-x 2 root root 64 Mar 29 05:54 destination
 robert ❱ ~ ❱ touch exists
 robert ❱ ~ ❱ docker run --rm -it -v "${PWD}/exists:/root/destination" -w /root --entrypoint /usr/bin/ls ubuntu -lad destination
-rw-r--r-- 1 root root 0 Mar 29 05:58 destination
 robert ❱ ~ ❱

TL;DR - It could be your spelling as it was for me.

Robert J
  • 840
  • 10
  • 20
0

This also seems to happen if you forget to actually include the file you want to mount in the source dir. I learned the hard way. No error message such as 'file not present', instead creates a directory in the destination.

Doug Fir
  • 19,971
  • 47
  • 169
  • 299
-1

I have same issue on my Windows 8.1

It turned out that it was due to case-sensitivity of path. I called docker-compose up from directory cd /c/users/alex/ and inside container a file was turned into directory.

But when I did cd /c/Users/alex/ (not Users capitalized) and called docker-compose up from there, it worked.

In my system both Users dir and Alex dir are capitalized, though it seems like only Users dir matter.

Alex Velickiy
  • 541
  • 1
  • 7
  • 22