Crafting multi-stage builds with Docker in Node.js

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 ?.

we are in a development era where Compiler and transpiler plays a vital role. especially, in javascript development environment. For Example, TypeScript and Babel.

Before multi-stage builds

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.

Dockerfile.dev

1FROM node:10.15.2-alpine
2WORKDIR /usr/src/app
3COPY package.json ./
4RUN npm install
5COPY . .
6RUN npm run dev

Dockerfile

1FROM node:10.15.2-alpine
2WORKDIR /usr/src/app
3COPY package.json ./
4RUN npm install
5COPY /usr/src/app/dist ./dist
6EXPOSE 4002
7CMD 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.

Using multi-stage builds

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.

Staging Build

1FROM node:10.15.2-alpine
2WORKDIR /usr/src/app
3COPY package.json ./
4COPY .babelrc ./
5RUN npm install
6COPY ./src ./src
7RUN 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.

Final Build

1FROM node:10.15.2-alpine
2WORKDIR /usr/src/app
3COPY package.json ./
4COPY .babelrc ./
5RUN npm install
6COPY --from=0 /usr/src/app/dist ./dist
7EXPOSE 4002
8CMD 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.

Naming build stages

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

Staging Docker Build

1FROM node:10.15.2-alpine AS appbuild
2WORKDIR /usr/src/app
3COPY package.json ./
4COPY .babelrc ./
5RUN npm install
6COPY ./src ./src
7RUN npm run build

Final docker Build

we refer the previous build with COPY --from=appbuild

1FROM node:10.15.2-alpine
2WORKDIR /usr/src/app
3COPY package.json ./
4COPY .babelrc ./
5RUN npm install
6COPY --from=appbuild /usr/src/app/dist ./dist
7EXPOSE 4002
8CMD npm start

Complete Dockerfile

1# Build Stage 1
2# This build created a staging docker image
3#
4FROM node:10.15.2-alpine AS appbuild
5WORKDIR /usr/src/app
6COPY package.json ./
7COPY .babelrc ./
8RUN npm install
9COPY ./src ./src
10RUN npm run build
11
12# Build Stage 2
13# This build takes the production build from staging build
14#
15FROM node:10.15.2-alpine
16WORKDIR /usr/src/app
17COPY package.json ./
18COPY .babelrc ./
19RUN npm install
20COPY --from=appbuild /usr/src/app/dist ./dist
21EXPOSE 4002
22CMD 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"
2
3services:
4 app:
5 container_name: app
6 restart: always
7 build: .
8 environment:
9 - PORT=4002
10 ports:
11 - "4002:4002"
12 links:
13 - mongo
14 mongo:
15 container_name: mongo
16 image: mongo
17 volumes:
18 - ./data:/data/db
19 ports:
20 - "27017:27017"

After that, Run the docker compose with the command,

1docker-compose up

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.

Screenshot 2019 10 05 at 3 48 11 PM

Screenshot 2019 10 05 at 3 48 36 PM

Complete Source code is available in this repository

To Read More

Building a Piano with React Hooks

In this article, we will see how to build a piano with react hooks. Building a P...

TypeScript Basics - The Definitive ...

In this article, we will learn some basics of typescript which helps you to deve...

Here's why podman is more secured t...

In this article we will see about podman and why it is more secured way to run c...

Building a Production - Ready Node....

In this article, we will see how to build a Nodejs, TypeScript Application and d...

Nginx for Front-end Developers

This article is to explain Nginx for Front-end Developers in a much simpler way....

What is gRPC ? How to implement gRP...

Everyone talks about gRPC. Have you ever wonder how it works or how to implement...