I'm still trying to improve my Docker builds. Here's an example for a multi-stage Docker build for React:

# base image
FROM node:12.13.1-alpine as compile-image

# install global packages
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
RUN npm install npm@latest -g
RUN npm install pnpm react-scripts@3.3.0 -g

# set working directory & give permissions to user `node`
RUN mkdir -p /usr/src/app && chown node:node /usr/src/app
WORKDIR /usr/src/app

# switch to non-root user & install dependencies
USER node
COPY package*.json /usr/src/app/
COPY pnpm-lock.yaml /usr/src/app/pnpm-lock.yaml
ENV PATH /usr/src/app/node_modules/.bin:$PATH
ENV PATH=$PATH:/home/node/.npm-global/bin
RUN pnpm install

# set environment to production, overwrite
# with docker-compose
ARG NODE_ENV=production
ENV NODE_ENV $NODE_ENV

# create build
COPY . /usr/src/app
RUN pnpm run build

# runtime image
FROM nginx:1.15.9-alpine

# update nginx conf with custom config
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx

# copy static files
COPY --from=compile-image /usr/src/app/build /usr/share/nginx/html

# expose port
EXPOSE 80

# run nginx
CMD ["nginx", "-g", "daemon off;"]

Further Reading πŸ”—︎