372

Supposed I have a Docker container and a folder on my host /hostFolder. Now if I want to add this folder to the Docker container as a volume, then I can do this either by using ADD in the Dockerfile or mounting it as a volume.

So far, so good.

Now /hostFolder contains a sub-folder, /hostFolder/subFolder.

I want to mount /hostFolder into the Docker container (whether as read-write or read-only does not matter, works both for me), but I do NOT want to have it included /hostFolder/subFolder. I want to exclude this, and I also want the Docker container be able to make changes to this sub-folder, without the consequence of having it changed on the host as well.

Is this possible? If so, how?

Golo Roden
  • 140,679
  • 96
  • 298
  • 425

12 Answers12

609

Using docker-compose I'm able to use node_modules locally, but ignore it in the docker container using the following syntax in the docker-compose.yml

volumes:
   - './angularApp:/opt/app'
   - /opt/app/node_modules/

So everything in ./angularApp is mapped to /opt/app and then I create another mount volume /opt/app/node_modules/ which is now empty directory - even if in my local machine ./angularApp/node_modules is not empty.

kernix
  • 7,970
  • 3
  • 28
  • 44
  • 5
    It works but i cannot remove these dirs inside container For example: i need to replace `/opt/app/node_modules/` with another directory with same name. Error happens: 'volume busy' – Stepan Yudin Sep 23 '16 at 17:00
  • 7
    Please note that order is important: if your image had files under `/opt/app/node_modules/` they will be ignored if you mount it after the parent directory. For exemple, if you want to include modules in the image, but be able to refresh it later, you would list volumes in this order: `- /opt/app/node_modules/` then the parent `- './angularApp:/opt/app'` – cyrilchampier Jan 25 '17 at 09:54
  • 74
    Don't forget the trailing slash! eg. /opt/app/node_modules doesn't work, it will be overwritten by ./angularApp/node_modules – Stingus Feb 03 '17 at 11:32
  • 1
    @kernix you're a king! – Joe Hany Apr 07 '17 at 23:19
  • I threw a `9p` virtio mount in the way too, seems the effective user docker tries to expose the volume under needs write permissions to the node_modules folder, even though its empty, and completely unused by anything except the docker container. – ThorSummoner Apr 12 '17 at 20:16
  • The problem I have with this is that I get the error : `container_linux.go:247: starting container process caused "process_linux.go:359: container init caused "rootfs_linux.go:54: mounting "..." to rootfs "..." at "/mnt/.../merged/tmp/ui/node_modules" caused "mkdir /mnt/.../merged/tmp/ui/node_modules: read-only file system"""` if someone doesn't have the node_modules folder – edi9999 May 11 '17 at 06:33
  • @kernix I've just tried your example. If I do that, then the only thing I see in the "app" folder inside the container is the "node_modules" folder (which is generated by `RUN npm install` in Dockerfile). But no other files show up in the "app" folder inside my container, although I have a bunch of files and folders on my filesystem in that folder the volume points to (angularApp). Should this still work? Win 10 Pro here, Docker version 17.09.0-ce – MrCroft Oct 08 '17 at 03:09
  • 1
    @cyrilchampier could you please *cite your sources* on *why the order of listing the volumes in the `docker-compose.yml `affects the way docker is doing the mounts"?* Because the only thing I was able to find is that docker is executing the mounts in [lexicographic order](https://github.com/moby/moby/issues/26051#issuecomment-242847347). I can not find a reference regarding the way they are listed in `docker-compose` file – ira Dec 13 '17 at 09:10
  • @ira I am not on the subject anymore. From what I remember mounting `- /root - /root/sub` is different than `- /root/sub - /root` – cyrilchampier Dec 13 '17 at 15:34
  • 1
    on my mac, when I remove node_modules then the container will remove it too; then the trick doesn't work anymore – emj365 Dec 14 '17 at 02:51
  • 1
    @cyrilchampier I believe that this is not true. You can check this by swapping `./angularApp:/opt/app'` and `/app/node_modules/` in the answer above. The results will be the same – ira Dec 14 '17 at 08:36
  • 3
    This trick used to work for me, but doesn't seem to anymore, even for the self-same Compose file. Curious if it also stopped working for others. – plinehan Aug 15 '18 at 19:55
  • 2
    Yep @plinehan, it stopped working for me too. Not sure how to ignore node_modules now. – Omar Bahareth Jan 13 '19 at 12:20
  • 18
    thanks! You saved my day! In case of using simple docker, not compos it would look like: `-v $(pwd):/build/ -v /build/node_modules ` – Bogdan Mart Mar 19 '19 at 13:19
  • This make my day :) – allucardster Aug 15 '19 at 22:47
  • 1
    Where is this behaviour documented? What `type` is implicitly used (volume, bind, tmpfs or npipe?) and is this behaviour the same as setting `nocopy` in the [volumes *LONG SYNTAX*](https://docs.docker.com/compose/compose-file/#long-syntax-3) ? – Kamafeather Oct 15 '19 at 16:18
  • 1
    Trick still works for me. However, I have trouble wrapping my head around WHY it works. My current understanding is as follows. `.:/app` creates a bind mount, and then `/app/node_modules/` creates an anonymous volume (confirmed with docker inspect) which maps an empty directory from source to the container `/app/node_modules`. Then HOW am I able to use my npm installed packages from my Dockerfile?! – Rainymood Feb 11 '20 at 13:32
  • 1
    It doesn't work with `docker-compose 1.25.0-rc3`, file syntax v3. – thisismydesign Apr 12 '20 at 21:59
  • 7
    I found the document, https://docs.docker.com/storage/volumes/#populate-a-volume-using-a-container.`If you start a container which creates a new volume, as above, and the container has files or directories in the directory to be mounted (such as /app/ above), the directory’s contents are copied into the volume.` – hiroshi Apr 18 '20 at 12:26
  • Good solution. Works for me. – Bishwas Mishra May 07 '20 at 03:35
  • 3
    Note that `node_modules` is NOT updated when you run `docker-compose build`! I was expecting that `/opt/app/node_modules/` would always be in-sync with the whatever the container has at this location. But the content is only copied over initially when the volume is created. So if your `package.json` changes and you re-build the container then the `node_modules` in the volume are not in-sync with the container anymore. – Niklas Gruhn Mar 16 '21 at 11:36
  • 1
    One strange effect I've noticed with this. If my host machine has no `node_modules` folder, after running the container with volumes as described here, it gets an *empty* `node_modules` folder. It's disconnected from the volume, so changes here are not reflected in the container (good). *However* changes to the parent folder still are, so if I unlink/delete the host's `node_modules` then the *container's* is deleted as well! – Mark Aug 30 '21 at 18:37
  • 1
    that does work, but the folder inside container is not writable. This method works better https://stackoverflow.com/a/71911280/3419751 – Anton Apr 18 '22 at 11:39
  • This is excellent. I've been looking for this for quite some time, but never knew how to describe it, such that i came across the correct answer, until today! – Matthew Setter May 10 '23 at 10:52
177

If you want to have subdirectories ignored by docker-compose but persistent, you can do the following in docker-compose.yml:

volumes:
  node_modules:
services:
  server:
    volumes:
      - .:/app
      - node_modules:/app/node_modules

This will mount your current directory as a shared volume, but mount a persistent docker volume in place of your local node_modules directory. This is similar to the answer by @kernix, but this will allow node_modules to persist between docker-compose up runs, which is likely the desired behavior.

Nate T
  • 2,658
  • 1
  • 20
  • 14
  • 43
    Named volumes are a superior approach. Protip: make sure you aren't using a hyphenated volume name. You will spend a small portion of your life in a debug nightmare, only to find out that you were once again fooled by a ridiculous nuance. – dustintheweb May 31 '17 at 18:44
  • 9
    "Note that docker-compose down will kill these persistent volumes." This is not true in Docker for Mac v 17.0.5+ (and possibly older versions). `docker-compose down` will remove the containers, but the volume will persist until you run something like `docker system prune` – BrDaHa Jul 06 '17 at 18:45
  • 6
    In case someone is interestead to understand *why* using the `node_modules:` named volume "ignores" the `/app/node_modules` in the docker container, may find this post usefull [How docker handles multiple mount types?](https://stackoverflow.com/a/47873699/2235814) – ira Dec 20 '17 at 10:46
  • 6
    I don't know whether I'm doing something wrong, but if I do this, start the container and then run npm install on the HOST machine, it changes the node_modules inside the CONTAINER too. I can't imagine why anyone would want to do that, but still I'm looking for a cleaner separation. – Renra Feb 23 '18 at 09:24
  • any luck @Renra? – Ascherer Apr 25 '18 at 23:21
  • 4
    @Renra had the exact same issue, my host machine would constantly write over node_modules even though I had the named volume... caused all sorts of incompatibility issues. Gave up on fixing it and worked around it by only copying my src folder instead of the whole repo – dancypants Jul 12 '18 at 08:03
  • 1
    I am setting the env var `ENV NODE_PATH /node_modules` to a dir outside my app. I have a `node_modules_cache:` under the top level `volumes`. I then have `node_modules_cache:/node_modules` under the service. - this seems to work for me. – Brent Greeff May 16 '20 at 22:04
  • I ran into an error: `node_modules is used in service "web" but no declaration was found in the volumes section.`. So, I had to use relative dir path, ` - ./node_modules:/app/node_modules` – Cory Robinson Aug 11 '20 at 17:46
43

For those trying to get a nice workflow going where node_modules isn't overridden by local this might help.

  1. Change your docker-compose to mount an anonymous persistent volume to node_modules to prevent your local overriding it. This has been outlined in this thread a few times.
services:
  server:
    build: .
    volumes:
      - .:/app
      - /app/node_modules
  1. This is the important bit we were missing. When spinning up your stack use docker-compose -V. Without this if you added a new package and rebuilt your image it would be using the node_modules from your initial docker-compose launch.
    -V, --renew-anon-volumes   Recreate anonymous volumes instead of retrieving
                               data from the previous containers.
squid_ink
  • 431
  • 4
  • 2
  • 2
    The note about `--renew-anon-volumes` is an important addition. There are cases where you want to mount your source code folder and exclude the node_modules folder by mounting it as an anonymous volume. The flag avoids the re-using the previously created anonymous volume so that changes to `package.json` will reflect correctly. – Arik Oct 14 '20 at 14:51
  • 4
    When using this approach, an empty `node_modules` directory is created on the host. How can we avoid that? – lmat - Reinstate Monica Jul 30 '21 at 18:58
28

To exclude a file, use the following

volumes:
   - /hostFolder:/folder
   - /dev/null:/folder/fileToBeExcluded
Frank Wong
  • 1,718
  • 2
  • 13
  • 12
  • 7
    Perfect solution for what I needed (to NOT map node_modules up to the host because symlink stuff broke on a Windows host). Pointing to /dev/null did not work, but creating a blank directory and mapping it to that worked perfectly and solved the issues I was having - thanks for this idea. – Liam Hammett Nov 24 '17 at 14:59
  • 8
    This doesn't work in Docker for Mac 18.09.0: `Cannot start service xxx: OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:402: container init caused \"rootfs_linux.go:58: mounting \\\"/dev/null\\\" to rootfs \\\"/var/lib/docker/overlay2/d15ed56ad020d408c63a1f6a6365dbb88d5d3b78a4605980d3faa9861102ef21/merged\\\" at [...] unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type`. Adding a trailing slash didn't help. – Blaise Nov 22 '18 at 08:39
  • 1
    I can confirm that it does not work either on Ubuntu 16.04 with Docker 18.09.3. – Dirk Mar 06 '19 at 17:23
  • 1
    That doesn't work for me. All it does is copy `/dev/null` into the container, as a device file. – Resigned June 2023 Jun 12 '19 at 22:04
  • 3
    Please note this works only for excluding FILES! /dev/null is a file and so should be the target, the error message is quite clear on this: `Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type`. This is not exactly what the OP was looking for, but it was very useful for me, since I needed to exclude a single file. – Avius Nov 12 '19 at 15:58
21

With the docker command line:

docker run \
    --mount type=bind,src=/hostFolder,dst=/containerFolder \
    --mount type=volume,dst=/containerFolder/subFolder \
    ...other-args...

The -v option may also be used (credit to Bogdan Mart), but --mount is clearer and recommended.

DS.
  • 22,632
  • 6
  • 47
  • 54
14

First, using the ADD instruction in a Dockerfile is very different from using a volume (either via the -v argument to docker run or the VOLUME instruction in a Dockerfile). The ADD and COPY commands just take a copy of the files at the time docker build is run. These files are not updated until a fresh image is created with the docker build command. By contrast, using a volume is essentially saying "this directory should not be stored in the container image; instead use a directory on the host"; whenever a file inside a volume is changed, both the host and container will see it immediately.

I don't believe you can achieve what you want using volumes, you'll have to rethink your directory structure if you want to do this.

However, it's quite simple to achieve using COPY (which should be preferred to ADD). You can either use a .dockerignore file to exclude the subdirectory, or you could COPY all the files then do a RUN rm bla to remove the subdirectory.

Remember that any files you add to image with COPY or ADD must be inside the build context i.e. in or below the directory you run docker build from.

Adrian Mouat
  • 44,585
  • 16
  • 110
  • 102
  • 1
    learn something new every day :-) the COPY and ADD work from the build context (not the host). Would it be possible to use a volume to mask the subFolder? The OP wants to mount the /hostFolder (I think), and make /hostFolder/subFolder disconnected. If there was a VOLUME for each, but only the hostFolder was 'mounted' (-v), would that isolate the subFolder changes to the container? – Greg Mar 21 '15 at 15:21
  • I'm not sure, I've never tried nesting volumes. I'm not convinced it's a good idea, but I'd have to look into it. – Adrian Mouat Mar 22 '15 at 09:42
  • 2
    I can't seem to find an example anywhere of what to put in a `.dockerignore` file to exclude a sub directory (or even a directory). Given that is the stated purpose of the file, it's quite odd. – Lukas Oberhuber Jul 02 '15 at 10:00
  • 1
    once you add any files, they still end up in some layers. So a remove of them will not help you hide them if security sensitive or reduce image size (because they will be in the underlying layers). There are options to `squash` the layers. One is https://github.com/goldmann/docker-squash and another is using `oc ex dockerbuild` from the openshift project. – akostadinov Oct 11 '16 at 07:43
13

for the people who also had the issue that the node_modules folder would still overwrite from your local system and the other way around

volumes:
  node_modules:
services:
  server:
    volumes:
      - .:/app
      - node_modules:/app/node_modules/

This is the solution, With the trailing / after the node_modules being the fix.

Daenor
  • 131
  • 1
  • 2
  • 1
    So this just maps your local `node_modules` folder to be inside `/app/node_modules` folder in the container? So inside the container, we have `/app/node_modules/node_modules`? Why? – smac89 Feb 01 '22 at 20:50
  • @smac89 read answer above. node_modules is named container, not a host dir folder. – logicOnAbstractions Nov 12 '22 at 16:52
8

Looks like the old solution doesn't work anymore(at least for me). Creating an empty folder and mapping target folder to it helped though.

volumes:
   - ./angularApp:/opt/app
   - .empty:/opt/app/node_modules/
holdbar
  • 81
  • 1
  • 1
5

I found this link which saved me: Working with docker bind mounts and node_modules.
This working solution will create a "exclude" named volume in docker volumes manager. The volume name "exclude" is arbitrary, so you can use a custom name for the volume intead exclude.

services:
    node:
        command: nodemon index.js
        volumes:
            - ./:/usr/local/app/
            # the volume above prevents our host system's node_modules to be mounted
            - exclude:/usr/local/app/node_modules/

volumes:
    exclude:

You can see more infos about volumes in Official docs - Use a volume with docker compose

1

This works for me Manjaro Linux, Docker Compose yml has v2.4 at the top since thats the last non-swarm version. I want to make sure that my snapshots folder on my host is excluded in the docker container. I do not have volumes section in my docker-compose, keeping it simple:

version: "2.4"    
services:
  filerun:
    volumes:
      - $DATAPOOL/users:/user-files
      - /user-files/snapshots/

On my host, the folder users/snapshots remains untouched. Within the container, the folder user-files/snapshots is just an empty folder.

zilexa
  • 11
  • 3
0

For a file, an env file specifically, that must exist inside the project, I used this solution:

    volumes:
      - ./empty.env:/app/.env

Where "empty.env" is just an empty file. It helped me to use the same branch in different environments, inside and outside the container.

-1

To exclude a mounted file contained in the volume of your machine, you will have to overwrite it by allocating a volume to this same file. In your config file:

services:
  server:
    build : ./Dockerfile
    volumes:
      - .:/app

An example in you dockerfile:

# Image Location
FROM node:13.12.0-buster
VOLUME /app/you_overwrite_file
  • What the OP wants is actually to overwrite/override a directory AT RUNTIME, e.g. when the container starts. The solution you propose basically deals with BUILDTIME. It seems like a bad idea to try to manage a runtime problem at buildtime. It may work in some cases, but favor answers above. – logicOnAbstractions Nov 12 '22 at 16:51