You should delete that volumes:
block entirely. It's unnecessary and can lead to inconsistent results.
volumes:
- ./app:/opt/app:delegated
This creates a bind mount: the /opt/app
directory in the container is the ./app
directory on the host. Anything that the Dockerfile does in that directory (likely its entire installation process) is hidden.
The bind mount isn't a Docker object and it won't show up in places like docker volume ls
.
volumes:
- notused:/opt/app/node_modules
This replaces the contents of your node_modules
directory with the contents of a Docker named volume. The contents of that named volume will take precedence over both what's in the image and what's in the bind-mounted parent directory. As far as Docker knows, this directory contains opaque user data and will never update it. That is, this setup causes Docker to ignore changes in your package.json
file, even if you re-run docker-compose build
.
This setup takes advantage of a special case in Docker named volumes: the very first time an empty named volume is mounted into a container, Docker copies the contents of the image into the volume. This only happens the very first time you run the container (it is not a generic pass-through into the image) and only on Docker named volumes (not bind mounts or volumes in other runtimes like Kubernetes).
Many setups use an anonymous volume here, just listing /opt/app/node_modules
with no colon at all under volumes:
. This behaves identically to a named volume, except it doesn't have a name.
volumes:
- ./app/package-lock.json:/opt/package-lock.json
This is a bind mount, just like the first case, but it mounts a file into a different directory. This is one place where the "inconsistent results" I mentioned up front can come in: if you want to use the built Docker image without separately distributing the application source, rearranging files with bind mounts means that the filesystem layout is different with and without the volumes.
The related trouble I more often see with a setup like this is doing some installation steps or moving files in a Dockerfile. Since the bind mounts hide everything in the container, any of this work will be lost. Imagine you RUN yarn build
; the image would contain an /opt/app/dist
directory but the bind mount hides that.
Usually the motivation for this setup is to work directly on host code without rebuilding the image. However, the volume mounts hide everything in the image build, so you may as well run an unmodified node
image; and if you're running code bind-mounted into an unmodified node
image, you may as well just run Node without Docker involved at all. A useful development setup can be to run databases and other dependencies in Docker but to use a normal non-container language runtime for day-to-day development.