Docker has became an inevitable tool for development. Everyday developers face new challenges for containerizing their applications.One of the important problems are containerizing the application for different enviroments. Crafting multi-stage builds with Docker in Node.js.
you can ask me, why do we need multi-stage build in node.js applications.why can't we just build a single stage image and deploy it to server ?.
Before the concept of multi-stage builds, application will have two Dockerfile. one is for development and another one is for production. this has been referred as a builder pattern. But maintaining two Dockerfiles is not ideal.
1FROM node:10.15.2-alpine2WORKDIR /usr/src/app3COPY package.json ./4RUN npm install5COPY . .6RUN npm run dev
1FROM node:10.15.2-alpine2WORKDIR /usr/src/app3COPY package.json ./4RUN npm install5COPY /usr/src/app/dist ./dist6EXPOSE 40027CMD npm start
Although, it solves the problem of development and production image builds. this will be expensive in the long run.it will take lots of spaces in local disk as well.
multi-stage build combines different environment Dockerfile into one to create a production build. For example, Staging build creates a compiled version of application source code and final build contains the compiled version deployed in the image container.
Let's see with an example of building muti-stage for Node.js with babel and MongoDB. Complete Source is available in this repository
Create a directory and initialize the Application with Express and Babel.
1FROM node:10.15.2-alpine2WORKDIR /usr/src/app3COPY package.json ./4COPY .babelrc ./5RUN npm install6COPY ./src ./src7RUN npm run build
Above command, takes the node:10.15.2-alpine as base image and copies all the source code along with babel config. it builds the compiled code and stores it in dist folder in a container.
1FROM node:10.15.2-alpine2WORKDIR /usr/src/app3COPY package.json ./4COPY .babelrc ./5RUN npm install6COPY --from=0 /usr/src/app/dist ./dist7EXPOSE 40028CMD npm start
This command, takes the compiled version from previous staging build and stores it in a new image container. the magic happens in line COPY --from=0
COPY --from=0 line copies just the build artifacts from previous stage into the new stage.
Instead of referring build stages with a Number, you can name them and use it for reference. For Example, we specify the stage build with the name appbuild
1FROM node:10.15.2-alpine AS appbuild2WORKDIR /usr/src/app3COPY package.json ./4COPY .babelrc ./5RUN npm install6COPY ./src ./src7RUN npm run build
we refer the previous build with COPY --from=appbuild
1FROM node:10.15.2-alpine2WORKDIR /usr/src/app3COPY package.json ./4COPY .babelrc ./5RUN npm install6COPY --from=appbuild /usr/src/app/dist ./dist7EXPOSE 40028CMD npm start
1# Build Stage 12# This build created a staging docker image3#4FROM node:10.15.2-alpine AS appbuild5WORKDIR /usr/src/app6COPY package.json ./7COPY .babelrc ./8RUN npm install9COPY ./src ./src10RUN npm run build1112# Build Stage 213# This build takes the production build from staging build14#15FROM node:10.15.2-alpine16WORKDIR /usr/src/app17COPY package.json ./18COPY .babelrc ./19RUN npm install20COPY --from=appbuild /usr/src/app/dist ./dist21EXPOSE 400222CMD npm start
Once, we complete the dockerfile. create docker compose to link multiple containers together.
Create a docker-compose.yml file and add the following,
1version: "3"23services:4 app:5 container_name: app6 restart: always7 build: .8 environment:9 - PORT=400210 ports:11 - "4002:4002"12 links:13 - mongo14 mongo:15 container_name: mongo16 image: mongo17 volumes:18 - ./data:/data/db19 ports:20 - "27017:27017"
After that, Run the docker compose with the command,
you can see the docker builds an intermediate image and use it to build a final one. Once it builds a final one,Docker deletes the intermediate images.
Complete Source code is available in this repository