Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should Dockerfile execute "npm install" and "npm run build" or should it only copy those files over?

I'm a little new to Docker and trying to wrap my head around some of the concepts.

In a lot of tutorials and articles (actually, almost all of them), this is a typical Dockerfile for a create-react-app and nginx configuration:

# CRA
FROM node:alpine as build-deps
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm install
COPY . ./
RUN npm run build

# Nginx
FROM nginx:1.12-alpine
COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Assuming everything works as expected, the image would be huge.

I had the idea of taking a slightly different approach. Run npm install && npm run build locally, and then have this as the Dockerfile:

FROM nginx:1.12-alpine
WORKDIR /opt/app-root

COPY ./nginx/nginx.conf /etc/nginx/
COPY ./build ./src/dist/

COPY ./node_modules .

USER 1001
EXPOSE 8080
ENTRYPOINT ["nginx", "-g", "daemon off;"]

Which approach is better? Whenever I run docker build -t app-test:0.0.1 ., it seems to me that the second approach is always faster.

like image 506
Mike Avatar asked Jan 15 '21 10:01

Mike


People also ask

What is the difference between npm install and npm run build?

npm install installs dependencies into the node_modules/ directory, for the node project you're working on. You can call install on another node. js project (module), to install it as a dependency for your project. npm run build does nothing unless you specify what "build" does in your package.

Do I need to run npm install before build?

If you are building a web application using ASP.NET Core (MVC, Pages, Blazor), you may rely on npm to minify and bundle your css and js files. This means that to build your application, you need to run an npm script such as npm run build before executing dotnet build . That's not convenient!

Does npm run build install packages?

The npm build is used to build a package, the synopsis is given in the next section. where <package-folder> is a folder that contains a package. json in its root. This is the plumbing command that is called by npm link and npm install.

What is npm run build?

npm run build creates a build directory with a production build of your app. Set up your favorite HTTP server so that a visitor to your site is served index. html , and requests to static paths like /static/js/main. <hash>. js are served with the contents of the /static/js/main.

How to avoid NPM install on every Docker build?

To avoid the npm install phase on every docker build just copy those lines and change the ^/opt/app^ to the location your app lives inside the container. That works. Some points though. ADD is discouraged in favor for COPY, afaik. COPY is even more effective.

Should I use NPM install or NPM install by default?

I recommend using it by default. Have a read how it is different than npm install in the official docs. The magic happens in && which will execute two commands in one run producing one Docker image layer. This layer will be then cached, so subsequent run of the same command (with the same package*.json) will use the cache.

How does Docker work with Node JS?

(A container has its own file system!) Docker executes your instructions one after the other, then creates a snapshot of the result and stores this as an image. You can then create a container from the image, start the container, and execute commands inside it. Here’s how I would build and run my Node.js app using the above Dockerfile:

How does Docker build an image?

So first off, Docker builds an image based on the instructions in your Dockerfile. Each line in the Dockerfile is an instruction for how Docker should build the image. For example, the copy instruction copies a file from the Docker context (e.g. your source code) into the container. (A container has its own file system!)


Video Answer


2 Answers

Building inside a container guarantees a predictable and reproducible build artifact. Running npm install on macOS and Linux can produce different node_modules, for example node-gyp.

People often build node_modules with multi-stage build (if the actual container you're trying to build is not a Node.js application). That is to say, your actual nginx application per se does not depend on Node.js, but the node_modules directory and its containing files instead. So we generate node_modules in a node container, and copy it to the new container (nginx).

Thus, everyone building with the multi-stage Dockerfile will produce exact same container. If you copy your local node_modules into a container during build, other coworkers will not be able to predict the content of node_modules.

like image 88
Birkhoff Lee Avatar answered Sep 28 '22 10:09

Birkhoff Lee


Should Dockerfile execute “npm install” and “npm run build” or should it only copy those files over?

TL;DR: It should always execute all necessary build commands in the "build step" of the multi-stage image!

Long answer:

In the first example "tutorials" Dockerfile that you posted, a multi-stage build is used. With multistage builds you can discard artifacts created in previous stages and only keep those files and changes in the final image that you really need. In this case, the installed "dev" packages are not copied into the final image, thus not consuming any space. The build folder will only contain the code and the node modules required at runtime without any dev dependencies that were required in the first step of the build to compile the project.

In your second approach, you are running npm install && npm run build ouside your Dockerfile and then copy your results into the final image. While this works, from a devops perspective it is not a good idea since you want to keep all required building instructions consistently in one place (preferably in one Dockerfile), so the next person building your image does not have to figure out how the compilation process works. Another problem with copying the build results from your local machine is that you may be running another OS with a different node version etc. and this can impact the build result. If you instead, as with the "tutorial" Dockerfile, conduct the build within the Dockerfile, you have full control over the OS and the environment (node version, node-sass libraries etc.) and everybody executing docker build will get the same compilation results (given that you pinpointed the node version of your Dockerfile base image, i.e. using FROM node:14.15.4-alpine as build-deps instead of merely FROM node:alpine as build-deps).

One last note on the evolution of Dockerfiles. In the past, it was actually the way to go to perform the compilation outside the Dockerfile (or in another separate Dockerfile) and then to copy all the results into your final image. This matches the second approach mentioned in your OP. But for all the shortcomings mentioned above the docker architects in 2017 invented multi-stage builds. Here are some enlightening quotes from the docker blog:

Before multi-stage build, Docker users would use a script to compile the applications on the host machine, then use Dockerfiles to build the images. Multi-stage builds, however, facilitate the creation of small and significantly more efficient containers since the final image can be free of any build tools. [And] External scripts are no longer needed to orchestrate a build.

The same idea is reiterated in the official docs:

It was actually very common to have one Dockerfile to use for development (which contained everything needed to build your application), and a slimmed-down one to use for production, which only contained your application and exactly what was needed to run it. This has been referred to as the “builder pattern”. Maintaining two Dockerfiles is not ideal. […] Multi-stage builds vastly simplify this situation! […] You only need the single Dockerfile. You don’t need a separate build script, either. Just run docker build. The end result is the same tiny production image as before, with a significant reduction in complexity. You don’t need to create any intermediate images and you don’t need to extract any artifacts to your local system at all.

like image 23
B12Toaster Avatar answered Sep 28 '22 11:09

B12Toaster