Docker Community Forums

Share and learn in the Docker community.

How volume mounting works in docker?

I have a very simple node js app and the project structure looks like this.

index.js

package.json

package-lock.json

Dockerfile

FROM node:12.18.2-alpine
WORKDIR /test-app
COPY package.json package-lock.json ./
RUN npm i
COPY . ./
EXPOSE 3000
ENTRYPOINT [ "node", "index.js" ]

docker-compose.yml

version: '3.2'

services:
  test-app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/test-app
      - "test_app_node_modules:/test-app/node_modules"
volumes:
  test_app_node_modules:
    driver: local

If you look at the volumes section in docker-compose.yml file, first I’m bind mounting my current directory on the host machine to the test-app directory on the container. This means :

  1. whatever files or directories that were inside my current dir will get reflected on the container dir and any changes made to the container dir will also get reflected back to the host dir.
  2. this means node_modules that were installed in the test-app dir of the container, during docker build, were overwritten as well.

and the next step in the volumes section is named volumes. This means:

  1. when the volume doesn’t exist, running for the first time, It should copy everything from test-app/node_modules inside the container to test_app_node_modules volume. But the test-app/node_modules is empty because step 1 overwrote it.
  2. which means we created an empty volume and mounted it to the container.

If this is so, it should be causing missing dependency error but my app is running properly. I’m not sure where I’m getting node_modules from.

Also, I see an empty node_modules folder in the host directory. I assume the reason behind this is "test_app_node_modules:/test-app/node_modules" looks for the node_modules in the container but it doesn’t exist so it creates one and as a result, it gets reflected back to the host dir.

I’m not able to grasp the idea of volume mounting. What is the flow here? How node_modules are begin stored into the volumes when there are none?

Did you take a look in /var/lib/docker/volumes/test_app_node_modules/_data and check wether any data is there?

Instead of reliying on the “copy on first use” mechanism of volumes, you might want to introduce an entrypoint script that a) checks wether a specific folder is empty and in case it is copy files from a temp folder to the target folder and b) start you node process. This will be more beginner friendly (who tend to use bind-mounts rather than volumes).

Do you think you can take a look at this example project?

I guess empty volumes will inherit files from the image instead of container. But i don’t see it anywhere in documentation.

Can you help a lazy guy and create the image and push it to dockerhub?

Did you check the folder’s content? If it’s populated than your observation might be validated. There is still a chance that the mount order is prone to a race condition, which played in your favor this time…

here is the docker image, but I’m using docke-compose for mounting volumes into container so you might have to clone the repo and build it yourself.

docker pull rawatnaresh/test-app:latest

yes, There’s data inside /var/lib/docker/volumes/test_app_node_modules/_data

I’m not sure no matter how many times you build it the results are always the same.

What do you think about this?

Here are the logs when i hit docker-compose up


naresh@naresh:~/Desktop/eg(master)$ docker-compose up
Creating network "eg_default" with the default driver
Creating volume "eg_test_app_node_modules" with local driver
Building test-app
Step 1/7 : FROM node:12.18.2-alpine
 ---> 057fa4cc38c2
Step 2/7 : WORKDIR /test-app
 ---> Running in dd9c24971f81
Removing intermediate container dd9c24971f81
 ---> 9fd168b96072
Step 3/7 : COPY package.json package-lock.json ./
 ---> faea6f3981d9
Step 4/7 : RUN npm i
 ---> Running in e62870b30b14
npm WARN test@1.0.0 No description
npm WARN test@1.0.0 No repository field.

added 50 packages from 37 contributors and audited 50 packages in 3.832s
found 0 vulnerabilities

Removing intermediate container e62870b30b14
 ---> 0e538ff07828
Step 5/7 : COPY . ./
 ---> f6038c6c15f5
Step 6/7 : EXPOSE 3000
 ---> Running in 12658de8319f
Removing intermediate container 12658de8319f
 ---> 025d402f5592
Step 7/7 : ENTRYPOINT [ "node", "index.js" ]
 ---> Running in dc9b9cde2c3f
Removing intermediate container dc9b9cde2c3f
 ---> f4341e6a4bbe
Successfully built f4341e6a4bbe
Successfully tagged eg_test-app:latest
WARNING: Image for service test-app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating eg_test-app_1 ... done
Attaching to eg_test-app_1
test-app_1  | Server started at 3000

Created and managed by Docker. You can create a volume explicitly using the docker volume create command, or Docker can create a volume during container or service creation.

When you create a volume, it is stored within a directory on the Docker host. When you mount the volume into a container, this directory is what is mounted into the container. This is similar to the way that bind mounts work, except that volumes are managed by Docker and are isolated from the core functionality of the host machine.

A given volume can be mounted into multiple containers simultaneously. When no running container is using a volume, the volume is still available to Docker and is not removed automatically. You can remove unused volumes using docker volume prune.

When you mount a volume, it may be named or anonymous. Anonymous volumes are not given an explicit name when they are first mounted into a container, so Docker gives them a random name that is guaranteed to be unique within a given Docker host. Besides the name, named and anonymous volumes behave in the same ways.

Volumes also support the use of volume drivers, which allow you to store your data on remote hosts or cloud providers, among other possibilities.

Bind mounts: Available since the early days of Docker. Bind mounts have limited functionality compared to volumes. When you use a bind mount, a file or directory on the host machine is mounted into a container. The file or directory is referenced by its full path on the host machine. The file or directory does not need to exist on the Docker host already. It is created on demand if it does not yet exist. Bind mounts are very performant, but they rely on the host machine’s filesystem having a specific directory structure available. If you are developing new Docker applications, consider using named volumes instead. You can’t use Docker CLI commands to directly manage bind mounts.

your explanation is different from what i asked.

Forget the responses lewish95! I suspect that it’s someones AI playgroup bot in early stages.

Yesterday I was not in the mood for a testdrive, though I tried it now.
I modified your docker-compose.yml snippet and replaced the build declaration with image and fired up the container.

Of course the container immediatly dies because I lack the applications in .:

internal/modules/cjs/loader.js:969
  throw err;
  ^

Error: Cannot find module '/test-app/index.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:966:15)
    at Function.Module._load (internal/modules/cjs/loader.js:842:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}

But back to the main objective: you are right, the volume is populated with data. Your observations are correct. Can you open an issue on dockers Github pages regarding this missing detail in the documentation?

Though, may I suggest something? You might want to consider to mimic the volumes copy-on-create mechanism in an entrypoint script… Store the files in a temporary folder and copy the files to the target if the target folder is empty.