Production Ready NodeJS build using Docker and npm
Sumit Bhanushali
Posted on December 12, 2023
An app with a smooth deployment process is reliable and has fewer bugs because it is easy to release fixes and is easier to set up in any environment. An app can lose all its charm if it is not reliable. When users want your app to work, it should work.
To set up a smooth deployment process, we will divide our process into three stages which will benefit Developers, DevOps and Users alike.
Build
The build Stage is when we are done with developing code and are confident for deployment. We thus run a build command that outputs an executable file that cannot be modified by us.Release
Release Stage is where we combine our build with config which makes the app able to run. These releases should be tagged with a unique ReleaseID.Run
Run Stage is where we run our release and make our app accessible to the outside world.
These stages should be strictly separated and any code change should trigger a new build, release and run stage. The Run stage should be as simple as possible. Any process or system restart can easily rerun our run stage without requiring manual intervention. This also simplifies rolling back to the previous release when required. Let's start with the first stage
Build
We will use Docker to build our app. We will be able to use the image generated from docker to deploy on any machine where docker is installed.
We will need a few npm scripts which will be used in our Dockerfile.
"prisma:generate": "prisma generate",
"start": "node .dist/index.js",
"build": "tsc",
Next we will create Dockerfile
. We will use a multi-stage build technique to keep our image size minimal.
FROM node:20-alpine AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build && npm run prisma:generate
##### STAGE 2 #####
FROM node:20-alpine
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/.dist ./.dist
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/package.json ./package.json
EXPOSE 3000
CMD ["npm", "start"]
These commands RUN npm run build && npm run prisma:generate
is where our Typescript code gets converted to Javascript and necessary prisma client files will be created.
In Stage 2, we will only copy files from .dist
folder and discard typescript files.
This config.env file will be different for different environments like UAT, Production, etc. We will be required to manually create/update this file before creating a Docker container.
We will use this command to build a Docker image, we are using the version from our package version to tag our images. In this case, our image name will be bloggo:0.1.0
docker image build -t bloggo:$(node -p \"require('./package.json').version\") .
We can further simplify the build process by introducing some npm scripts.
Build image by just running npm run build
"build:image": "docker image build -t bloggo:$(node -p \"require('./package.json').version\") ."
Upgrade the version accordingly and build an image. Note that npm version requires that all changes are already committed and this command will create a new commit with the version number as the tag
"build:major": "npm version major && npm run build:image",
"build:minor": "npm version minor && npm run build:image",
"build:patch": "npm version patch && npm run build:image"
Full npm scripts for reference
"scripts": {
"prisma:generate": "prisma generate",
"start": "node .dist/index.js",
"start:dev": "nodemon --exec node -r ts-node/register --env-file=config.env index.ts",
"build": "tsc",
"build:image": "docker image build -t bloggo:$(node -p \"require('./package.json').version\") .",
"build:major": "npm version major && npm run build:image",
"build:minor": "npm version minor && npm run build:image",
"build:patch": "npm version patch && npm run build:image"
}
Release
Release = Build + Config
This command creates a Release version for us. Here Docker's ContainerID can be treated as ReleaseID. We are using --env-file
to load config.env
file in our container
docker container create -p 3000:3000 --env-file config.env bloggo:0.1.0
Run
To fetch ContainerID, which we will use to run
our container
docker container ps
To start container and add restart policy
docker container start a897dbba5329 && docker update --restart unless-stopped a897dbba5329
With this setup, we will be easily be able to generate deployment builds and deploy to any number of servers with just few changes in config (if required) reliably
Link to the project that will be used in this post: https://github.com/sumitbhanushali/bloggo
Posted on December 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.