NestJs and Docker with some AWS (Part 1)

Andrew Allison
6 min readDec 22, 2022

--

Overview

I just thought I would put a lot of this into a medium article as it’s something I’m playing about with a bit over Christmas and hoped it might help others doing similar.
The end goal is to have an API that works in a docker container and can be deployed to an AWS instance. We will build it up over the next few weeks. In this instalment, we will simply get a local instance of a NestJs server up and running. I’ll add additional articles as I get them done.

Pre-Requisites

This is a slightly more advanced article but should still be achievable by someone with a decent understanding of development processes. I’m going to assume you are comfortable with TypeScript, the console, and making HTTP requests. You will need npm, docker and an Ide such as VsCode or WebStorm. I’m using windows and have never strayed down the Linux/Mac path so please be aware this might not go well on them platforms but I think most of it should still be straightforward enough

It all beings with visiting the NestJs site

npm i -g @nestjs/cli
nest new nest-api-docker
I used npm but it should be your choice

cd into the folder or open the folder with Vscode or Webstorm. My preference is WebStorm it’s not free but it just seems to make me much more productive than VsCode for some reason. I’ve tried to find plugins to match etc. I can’t seem to make VScode become what Webstorm is for me.

From here I would run

npm run start:dev

To check that everything is up and running. If successful you should get output similar to this:

Up and running output

The first thing I want to add is an end-point to show info about the server.

npm i nestjs-real-ip

Then update the app.controller.ts file to look like this

import { Controller, Get } from '@nestjs/common';
import { RealIP } from 'nestjs-real-ip';

@Controller()
export class AppController {
@Get('')
getInfo(@RealIP() ip: string) {
return {
version: process.env.npm_package_version,
env: process.env.NODE_ENV,
ip,
date: new Date().toISOString(),
};
}
}

This will tell us what version of the server is based on the package.json file, the env it is running under, and the requesting user's IP.

We will also tweak the main.ts file to allow us some flexibility with the port. I’ve defaulted to 6000 as it stops a lot of conflict with the 3000 that everything, like to, use

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

const port = process.env.PORT || 6000;

async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(port);
}

bootstrap()
.catch((error) => console.error(error))
.then(() => console.log(`Listening on http://localhost:${port}`));

You should now see output like that below when you run the dev command

You can then use postman or any similar HTTP request plugins to hit the endpoint

output from the request

We are now going to Dockerize this bad boy. Start by creating a Dockerfile and a .dockerignorein the root of the project. Most of this is stolen from the Tom Ray article in the references. There is much better information about why this is all set up the way it is.

Add the following to the DockerFile

###################
# BUILD FOR LOCAL DEVELOPMENT
###################

FROM node:18-alpine As development

# Create app directory
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY --chown=node:node package*.json ./

# Install app dependencies using the `npm ci` command instead of `npm install`
RUN npm ci

# Bundle app source
COPY --chown=node:node . .

# Use the node user from the image (instead of the root user)
USER node

###################
# BUILD FOR PRODUCTION
###################

FROM node:18-alpine As build

WORKDIR /usr/src/app

COPY --chown=node:node package*.json ./

# In order to run `npm run build` we need access to the Nest CLI which is a dev dependency. In the previous development stage we ran `npm ci` which installed all dependencies, so we can copy over the node_modules directory from the development image
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules

COPY --chown=node:node . .

# Run the build command which creates the production bundle
RUN npm run build

# Set NODE_ENV environment variable
ENV NODE_ENV production

# Running `npm ci` removes the existing node_modules directory and passing in --only=production ensures that only the production dependencies are installed. This ensures that the node_modules directory is as optimized as possible
RUN npm ci --only=production && npm cache clean --force

USER node

###################
# PRODUCTION
###################

FROM node:18-alpine As production

# Copy the bundled code from the build stage to the production image
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

in the .dockerignorefile add

Dockerfile
.dockerignore
node_modules
npm-debug.log
dist

Run this command from the terminal. This will take a few minutes with a lot of console output

docker build -t nest-api-docker .
# followed by
docker run -p 6000:6000 nest-api-docker

Next, we will add docker-compose.yml file with the following contents

version: '3'
services:
nest-api-docker:
environment:
- NODE_ENV=development
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/usr/app
container_name: nest-api-docker
expose:
- "6000"
ports:
- "6000:6000"

We can now run

docker compose up --build

This should spin us up a working container. NOTE: make sure you set the sharing permission on the folder when asked or you might get some errors.

Output from docker compose

I would also recommend the desktop version of Docker for people who like GUI’s more than commands. You get some cool output and lots of controls

Output from docker desktop

GIT

We will just add a few things that will help with git and overall controlling commits etc. Run the next command from a terminal and add these packages

npm i -D @commitlint/config-conventional commitizen commitlint conventional-github-releaser

Add the following item to the package.json file after the jest one and just before the final funky brace

...JEST, 
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

This will allow us to use commitizen style conventional commits which not only keep the commits consistent but allow for automation of release notes etc. later on. Run the following commands to stage all the changes and then follow the commit prompts

git add .
git-cz

From here I created a Github repo so we have somewhere to keep the code for now.

Git hub repo instructions

Follow the last 2 lines as the rest should already be in place

git remote add origin git@github.com:AndrewAllison/nest-api-docker.git
git push -u origin main

You should now have access to the public repo at https://github.com/AndrewAllison/nest-api-docker

Conclusion

Ok so this part was relatively short and sweet we will move on to the next part which should be coming in the following days. Take care and See you soon.

References

--

--

Andrew Allison

15 plus years of professionally putting characters in places that produces great software systems. A life time of hacking on computers.