I have the following Dockerfile for a React app:

## base image
FROM node:11.12.0-alpine

## set working directory
WORKDIR /usr/src/app

## add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH

## install and cache app dependencies
COPY package.json /usr/src/app/package.json
COPY package-lock.json /usr/src/app/package-lock.json
RUN npm install react-scripts@3.0.1 -g --silent
RUN chown -R node:node .
USER node
RUN npm ci

## start app
CMD ["npm", "start"]

After setting up the work directory I copy the package.json and package-lock.json to Docker. Then I install a global package (react-scripts). After that, I switch the owner of the working directory to the non-root user node.
Then I install the rest of the packages with npm ci as a normal user and start the app.

What happens when I want to add another package to my dependencies?

When I try to install a package locally, I get permission errors:

Missing write access to services/client/node_modules
errno -13
Error: EACCESS: permission denied, access 'services/client/node_modules'

What happens when I update package.json and then run docker-compose build?

npm ERR! cipm can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.

The command npm ci installs packages from a clean slate and relies on package-jock.json:

This command is similar to npm-install, except it’s meant to be used in automated environments such as test platforms, continuous integration, and deployment – or any situation where you want to make sure you’re doing a clean install of your dependencies. It can be significantly faster than a regular npm install by skipping certain user-oriented features. It is also more strict than a regular install, which can help catch errors or inconsistencies caused by the incrementally-installed local environments of most npm users.


Install the npm package via Docker/docker-compose. For example:

docker-compose run --rm client sh -c 'npm install'

(You can see my docker-compose.yml file on GitHub.)

Then rebuild the docker images and run them:

docker-compose up -d --build

Further Reading