UPDATE: found a good explanation of the "why". See: the section on Dealing With Node Modules.
TLDR; lets you run the app in and out of the container without worry that the OS-specific items in node_modules will not work between the host and the container.
Original (wrong as pointed out in the comments) answer:
This is to take advantage of the caching mechanism in Docker. Typically you'll have the following in your Dockerfile:
1. RUN apt-get #bunch of stuff you want installed
2. COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
3. RUN npm install --silent && mv node_modules ../
4. COPY . .
5. CMD node index.js
Docker builds its images in "layers", you can think of each layer being added as each line in the Dockerfile is executed.
Then what we are saying in snipped above is:
- Run
apt-get and commit the layer to the cache
- Next, copy the
package.json file from the host to the container, commit the layer
- Run
npm install, then move the node_modules folder up one directory, npm will recursively search upward until it finds an node_modules folder, so your app won't care. Commit this layer.
- Copy your source code contained in the current folder, to the container, commit this layer
- Finally, when your container is run, use the command
node index.js
The beauty of this system is that Docker will re-build a container only from the point where things have changed since the last time the container was built. So in our case, it'll only run npm install… if the previous layer had changes, say an updated package.json. If not, then it won't execute the npm install command, even if, say, index.js might have changed.
Of course, keep in mind you should have node_modules listed in your .dockerignore file so that the COPY . . doesn't try to pick it up.