Develop a CI/CD Pipeline for NestJS Backend Using CircleCI and AWS EC2
Automate Your NestJS Backend Deployment: A Comprehensive Guide to Setting Up CI/CD with CircleCI and AWS EC2
Hey there, fellow developers! Ever wondered how to make deploying your NestJS backend a breeze? Well, you're in the right place. In this blog, we'll walk you through setting up a simple Continuous Integration/Continuous Deployment (CI/CD) pipeline using CircleCI and AWS EC2. No fancy jargon, just easy steps. We'll cover Prisma ORM, Postgres, unit tests, end-to-end tests, and Nginx for deployment. Say goodbye to deployment headaches and hello to smooth sailing with your NestJS projects! Let's get started.
How to setup your project
.
├── node_modules
├── dist
├── .circleci/
│ ├── config.yml
│ ├── docker-compose-test.yaml
│ ├── e2e-test.env
│ ├── jest-e2e.json
│ └── test.env
├── deploy/
│ ├── default.conf.template
│ ├── docker-compose-deploy.yaml
│ ├── migrate_db.sh
│ ├── nestjsapp.deploy.Dockerfile
│ ├── reboot_app.sh
│ └── start_containers.sh
├── src/
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
│ └── app.e2e.spec.ts
└── .env
OK! Lets describe each directory
.circleci - config files required for circle ci integration. Including the config file
deploy - config files and scripts required to deploy the app in the remote server
src - your app source code
test - end to end tests in your app
.env - environment variables
dist - artifacts or transpiled javascript code of your app which is ready to run in production server
node_modules - the heaviest folder in the universe 😁
First things first, configure the EC2 instance
What you need ?
An EC2 instance with Ubuntu 20.04 LTS
A private key to SSH to your instance (.pem file)
ports 22 and 443 open in the AWS network security for your instance
A domain name (since we are deploying in prod, we need SSL and domain)
SSL certificate (certificate and private key)
Make the server ready for deployment
Install docker on the server. Follow the official docker installation steps below
Official Docker Installation Steps for UbuntuRun docker post installation steps from the official doc below
Docker Ubuntu Post Installation Stepsdocker volume create pgdata
#Navigate to home directory and create a directory for copying your app artifacts cd $HOME && mkdir my_nest_js_prod_app #my_nest_js_prod_app is where you store prod files
Copy the certificate and key. Make sure you have certificate (.crt) and key file (.key) for your domain and copy the certificate file and key file to /etc/ssl/<your-domain>/
eg: /etc/ssl/<your-domain>/certificate.crt
/etc/ssl/<your-domain>/key.key
Note: For automatic deployment and Nginx template file we store the domain name in an .env file.
Integration - Unit and E2E tests before deploying.
Integration testing, including unit and end-to-end tests, is crucial in the CI/CD pipeline for deploying backend apps. Unit tests catch bugs early, aid code maintainability, and provide rapid feedback. End-to-end tests validate the entire system, ensuring seamless component interaction and a reliable user experience. Together, these tests enhance code quality, support continuous improvement, and instill confidence in the deployed application's functionality and stability.
How to use CircleCI for integration ? the .circleci/config.yml file.
OK!. Lets see how we can configure the circleci config file for integration.
version: 2.1
jobs:
e2e_test:
docker:
- image: cimg/node:18.17.1
- image: cimg/postgres:15.2
environment:
POSTGRES_USER: postgres_test
POSTGRES_PASSWORD: test_pw
POSTGRES_DB: demo1_test
steps:
- checkout
- restore_cache:
keys:
- v1-yarn-deps-e2e_tests-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run:
name: "Copying test.env File"
command: |
cp .circleci/e2e-test.env .env
- run:
name: "Installing Dependencies"
command: |
yarn install
- save_cache:
key: v1-yarn-deps-e2e_tests-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: "Generating prisma models"
command: |
yarn prisma generate
- run:
name: "Running Migrations"
command: |
yarn prisma migrate deploy
- run:
name: "Running Seed"
command: |
yarn seed 2
- run:
name: "Running E2E Tests"
command: |
yarn jest --config ./.circleci/jest-e2e.json
- store_test_results:
path: junit.xml
unit_test:
machine:
image: ubuntu-2004:current
docker_layer_caching: true
steps:
- checkout
- restore_cache:
keys:
- v1-yarn-deps-unit_tests-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run:
name: "Copying test.env File"
command: |
cp .circleci/test.env .env
- run:
name: "Building Latest Docker Image"
command: |
docker build -t 'demo/app-backend-api-node' -f 'deploy/nestjsapp.deploy.Dockerfile' .
- run:
name: "Starting Test Docker Containers"
command: |
docker-compose -f .circleci/docker-compose-test.yaml -p 'app_test' --env-file .env up -d
- run:
name: "Installing Dependencies"
command: |
docker run --interactive --volume $PWD:/app:rw demo/app-backend-api-node yarn install
- save_cache:
key: v1-yarn-deps-unit_tests-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: "Generating prisma models"
command: |
docker compose -f '.circleci/docker-compose-test.yaml' -p 'app_test' --env-file .env run app yarn prisma generate
- run:
name: "Running Unit Tests"
command: |
docker compose -f '.circleci/docker-compose-test.yaml' -p 'app_test' --env-file .env run app yarn test --forceExit
- store_test_results:
path: junit.xml
workflows:
test:
jobs:
- unit_test
- e2e_test
Docker Images Setup:
Utilizes two Docker images:
cimg/node:18.17.1
for Node.js application code andcimg/postgres:15.2
for PostgreSQL database.Configures PostgreSQL environment variables for testing (
POSTGRES_USER
,POSTGRES_PASSWORD
,POSTGRES_DB
).
Checkout and Caching:
Checks out the code from the repository.
Restores cached dependencies based on the
yarn.lock
file to speed up the process.
Environment Setup:
- Copies the
e2e-test.env
file to.env
for environment configuration.
- Copies the
Dependency Installation:
- Installs project dependencies using
yarn install
.
- Installs project dependencies using
Prisma Models Generation:
- Generates Prisma models with the command
yarn prisma generate
.
- Generates Prisma models with the command
Database Setup:
Runs database migrations using
yarn prisma migrate deploy
.Seeds the database with sample data using
yarn seed 2
.
E2E Testing:
- Executes end-to-end tests using Jest with the configuration specified in
.circleci/jest-e2e.json
.
- Executes end-to-end tests using Jest with the configuration specified in
Test Results Storage:
- Stores the test results in
junit.xml
for further analysis.
- Stores the test results in
unit_test Job:
Machine Setup:
Uses an Ubuntu 20.04 image for building Docker containers.
Enables Docker layer caching to optimize dependency management.
Checkout and Caching:
Checks out the code.
Restores cached dependencies based on the
yarn.lock
file.
Environment Setup:
- Copies the
test.env
file to.env
for environment configuration.
- Copies the
Docker Container Setup:
Builds the Docker image with the specified Dockerfile (
deploy/nestjsapp.deploy.Dockerfile
).Starts Docker containers using Docker Compose with the configuration in
.circleci/docker-compose-test.yaml
.
Dependency Installation:
- Installs project dependencies within the Docker container using
docker run
.
- Installs project dependencies within the Docker container using
Prisma Models Generation:
- Generates Prisma models within the Docker container using
docker compose
.
- Generates Prisma models within the Docker container using
Unit Testing:
- Runs unit tests using the command
docker compose ... yarn test --forceExit
within the Docker container.
- Runs unit tests using the command
Test Results Storage:
- Stores the test results in
junit.xml
for further analysis.
- Stores the test results in
Workflow:
- Defines a workflow named
test
that orchestrates the execution of theunit_test
ande2e_test
jobs in sequence.
This configuration ensures a comprehensive testing process for both unit and end-to-end scenarios, integrating seamlessly into a CI/CD pipeline for backend applications. The use of Docker containers, caching, and specific testing configurations enhances efficiency and reliability throughout the testing and deployment processes.
.circleci/docker-compose-test.yaml
version: '3.8'
services:
app:
build:
context: ../deploy/
dockerfile: nestjsapp.deploy.Dockerfile
volumes:
- ../:/app:rw
restart: always
ports:
- '${APP_PORT}:${APP_PORT}'
depends_on:
- database
networks:
- default
command: ['tail', '-f', '/dev/null']
database:
image: postgres:15.2-alpine
restart: always
ports:
- '${POSTGRES_PORT}:5432'
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DATABASE}
networks:
- default
networks:
default:
name: ${NETWORK_NAME}
Services Section:
app
Service:
Build Configuration:
- The service is built from the context specified by
../deploy/
using the Dockerfile namednestjsapp.deploy.Dockerfile
.
- The service is built from the context specified by
Volume Mounting:
- Mounts the parent directory (
../
) to the/app
directory inside the container with read and write permissions.
- Mounts the parent directory (
Container Restart Policy:
- Configures the service to restart always.
Port Mapping:
- Maps the host's
${APP_PORT}
to the container's${APP_PORT}
for accessing the application.
- Maps the host's
Dependency on
database
Service:- Specifies that the
app
service depends on thedatabase
service, ensuring the database container is started before the application container.
- Specifies that the
Network Configuration:
- Assigns the service to the default Docker network (
default
).
- Assigns the service to the default Docker network (
Command Configuration:
- Overrides the default command with
['tail', '-f', '/dev/null']
, essentially keeping the container running in the foreground.
- Overrides the default command with
database
Service:
Image Configuration:
- Pulls the
postgres:15.2-alpine
image from Docker Hub.
- Pulls the
Container Restart Policy:
- Configures the service to restart always.
Port Mapping:
- Maps the host's
${POSTGRES_PORT}
to the container's PostgreSQL default port (5432
) for accessing the database.
- Maps the host's
Environment Variables:
- Sets environment variables for PostgreSQL user, password, and database name based on the provided variables.
Network Configuration:
- Assigns the service to the default Docker network (
default
).
- Assigns the service to the default Docker network (
Networks Section:
- Defines a Docker network named
${NETWORK_NAME}
(variable substitution) and assigns it as the default network for the services.
Variable Substitution:
- Variables such as
${APP_PORT}
,${POSTGRES_PORT}
,${POSTGRES_USER}
,${POSTGRES_PASSWORD}
,${POSTGRES_DATABASE}
, and${NETWORK_NAME}
are used for dynamic configuration. These should be defined elsewhere or in an environment file.
This Docker Compose file sets up two services, app
and database
, and defines a network for communication between them. The app
service runs a Node.js application using a custom Dockerfile, while the database
service uses a PostgreSQL container. The services are configured to restart always, and the necessary ports and network connections are established for seamless communication. Variable substitution allows for flexibility and customization based on specific deployment requirements.
.circleci/e2e-test.env file
NETWORK_NAME='app_test'
TESTING_PROJECT_NAME='app_e2e_test'
POSTGRES_USER=postgres_test
POSTGRES_PASSWORD=test_pw
POSTGRES_DATABASE=demo1_test
POSTGRES_PORT=5432
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DATABASE}?schema=public"
APP_PORT=3000
.circleci/jest-e2e.json file
{
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "../test",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": [
"ts-jest",
{
"astTransformers": {
"before": [
"test/transformer.js"
]
}
}
]
},
"moduleNameMapper": {
"^src/(.*)": "<rootDir>/../src/$1"
},
"reporters": [
"default",
"jest-junit"
]
}
.circleci/test.env file
NETWORK_NAME='app_test'
TESTING_PROJECT_NAME='app_e2e_test'
POSTGRES_USER=postgres_test
POSTGRES_PASSWORD=test_pw
POSTGRES_DATABASE=demo1_test
POSTGRES_PORT=5432
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@database:${POSTGRES_PORT}/${POSTGRES_DATABASE}?schema=public"
APP_PORT=3000
OK!. Lets move to deploy.
First things first. You need to provide your EC2 instance private key (.pem file) to CircleCi project. Need to store instance domain (host), login username (ubuntu) in CircleCi environment variables. (SSH_USER_PROD,SSH_HOST_PROD)
CircleCI config for deployment
version: 2.1
parameters:
remote_workingdir:
type: string
default: "my_nest_js_prod_app"
archive_name:
type: string
default: "build.tar"
jobs:
deploy_prod:
machine:
image: ubuntu-2004:current
docker_layer_caching: true
steps:
- checkout
- restore_cache:
keys:
- v1-yarn-deps-build-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run:
name: "Building Latest Docker Image"
command: |
docker build -t 'demo/app-backend-api-node' -f 'deploy/nestjsapp.deploy.Dockerfile' .
- run:
name: "Installing Dependencies"
command: |
docker run --interactive --volume $PWD:/app:rw demo/app-backend-api-node yarn install
- run:
name: "Generating prisma models"
command: |
docker run --interactive --volume $PWD:/app:rw demo/app-backend-api-node yarn prisma generate
- save_cache:
key: v1-yarn-deps-build-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: "Building App"
command: |
docker run --interactive --volume $PWD:/app:rw demo/app-backend-api-node yarn build
- run:
name: "Archiving Build"
command: |
tar cvf << pipeline.parameters.archive_name >> deploy/
tar rvf << pipeline.parameters.archive_name >> dist/
tar rvf << pipeline.parameters.archive_name >> node_modules/
tar rvf << pipeline.parameters.archive_name >> prisma/
tar rvf << pipeline.parameters.archive_name >> nest-cli.json
tar rvf << pipeline.parameters.archive_name >> package.json
tar rvf << pipeline.parameters.archive_name >> tsconfig.build.json
tar rvf << pipeline.parameters.archive_name >> tsconfig.json
tar rvf << pipeline.parameters.archive_name >> yarn.lock
- run:
name: "Copying Build To Remote Server"
command: |
scp << pipeline.parameters.archive_name >> $SSH_USER_PROD@$SSH_HOST_PROD:<< pipeline.parameters.remote_workingdir >>
- run:
name: "Unzipping Build"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && tar xvf << pipeline.parameters.archive_name >>"
- run:
name: "Creating Symlink for .env"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && ln -sf ../.env deploy"
- run:
name: "Starting Containers"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && sh deploy/start_containers.sh"
- run:
name: "Migrating Database"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && sh deploy/migrate_db.sh"
- run:
name: "Rebooting App Container"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && sh deploy/reboot_app.sh"
- run:
name: "Removing The Archive"
command: |
ssh $SSH_USER_PROD@$SSH_HOST_PROD "cd << pipeline.parameters.remote_workingdir >> && rm << pipeline.parameters.archive_name >>"
workflows:
production:
jobs:
- deploy_prod:
requires:
- unit_test
- e2e_test
filters:
branches:
only:
- main
Parameters Section:
remote_workingdir
:Type: string
Default: "my_nest_js_prod_app"
Represents the remote working directory where the application will be deployed.
archive_name
:Type: string
Default: "build.tar"
Specifies the name of the archive file that will be created for deployment.
Jobs Section:
deploy_prod
Job:
Machine Setup:
Uses an Ubuntu 20.04 image for building Docker containers.
Enables Docker layer caching to optimize dependency management.
Checkout and Caching:
Checks out the code from the repository.
Restores cached dependencies based on the
yarn.lock
file.
Docker Image Build:
- Builds a Docker image tagged as 'demo/app-backend-api-node' using the specified Dockerfile (
deploy/nestjsapp.deploy.Dockerfile
).
- Builds a Docker image tagged as 'demo/app-backend-api-node' using the specified Dockerfile (
Dependency Installation:
- Installs project dependencies within the Docker container using
docker run
.
- Installs project dependencies within the Docker container using
Prisma Models Generation:
- Generates Prisma models within the Docker container.
Dependency Caching:
- Saves the cache of dependencies for future builds.
Build Application:
- Builds the application using the command
yarn build
within the Docker container.
- Builds the application using the command
Archiving Build:
- Archives the build files, including
dist/
,node_modules/
, and configuration files, into a tar file specified by the parameterarchive_name
.
- Archives the build files, including
Copy Build to Remote Server:
- Uses
scp
to copy the archive to the specified remote working directory on the production server.
- Uses
Unzipping Build:
- Connects to the production server via SSH and extracts the archive.
- Creating Symlink for .env:
- Creates a symbolic link for the
.env
file within the deployed directory.
- Starting Containers:
- Executes a script (
start_
containers.sh
) on the production server to start Docker containers.
- Migrating Database:
- Executes a script (
migrate_
db.sh
) on the production server to perform database migrations.
- Rebooting App Container:
- Executes a script (
reboot_
app.sh
) on the production server to reboot the application container.
- Removing The Archive:
- Removes the archive file from the production server to clean up after deployment.
Workflows Section:
production
Workflow:
Job Dependencies:
- The
deploy_prod
job is triggered after the successful completion of bothunit_test
ande2e_test
jobs.
- The
Branch Filtering:
- The workflow is only triggered for the
main
branch.
- The workflow is only triggered for the
This CircleCI configuration file defines a job (deploy_prod
) responsible for deploying a Nest.js application to a production server. It encompasses steps such as building Docker images, installing dependencies, archiving the build, copying files to a remote server, and executing deployment scripts. The workflow ensures deployment only on the main
branch after passing both unit and end-to-end tests. Slack notifications are incorporated to communicate the deployment status. The use of parameters enhances flexibility and customization in the deployment process.
deploy/docker-compose-deploy.yaml file
version: '3.8'
services:
app:
build:
context: ./
dockerfile: nestjsapp.deploy.Dockerfile
volumes:
- ../:/app:rw
- /var/log/:/app/logs/:rw
restart: always
ports:
- '${APP_PORT}:${APP_PORT}'
depends_on:
- database
networks:
- default
env_file:
- .env
environment:
- NODE_ENV=${NODE_ENV}
command: ['node', 'dist/src/main.js']
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:${APP_PORT}/health']
interval: 1m30s
timeout: 10s
retries: 3
start_period: 60s
database:
image: postgres:15.2-alpine
restart: always
ports:
- '5432:5432'
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DATABASE}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test:
[
'CMD-SHELL',
"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DATABASE}'",
]
interval: 1m30s
timeout: 30s
retries: 5
start_period: 30s
networks:
- default
nginx:
image: nginx:1.25.2
ports:
- 443:443
depends_on:
app:
condition: service_started
healthcheck:
test: ['CMD', 'service', 'nginx', 'status']
interval: 1m30s
timeout: 30s
retries: 5
start_period: 30s
environment:
- APP_PORT
- DOMAIN
volumes:
- ./default.conf.template:/etc/nginx/templates/default.conf.template:ro
- '/var/log/nginx:/var/log/nginx'
- '/etc/ssl:/etc/ssl:ro'
networks:
- default
restart: always
networks:
default:
name: ${NETWORK_NAME}
volumes:
pgdata:
external: true
Services Section:
app
Service:
Build Configuration:
- Builds the service from the current context using the Dockerfile
nestjsapp.deploy.Dockerfile
.
- Builds the service from the current context using the Dockerfile
Volume Mounting:
Mounts the parent directory (
../
) to/app
inside the container with read and write permissions.Mounts the host's
/var/log/
to/app/logs/
inside the container for log storage.
Container Restart Policy:
- Configures the service to restart always.
Port Mapping:
- Maps the host's
${APP_PORT}
to the container's${APP_PORT}
for accessing the application.
- Maps the host's
Dependency on
database
Service:- Specifies that the
app
service depends on thedatabase
service, ensuring the database container is started before the application container.
- Specifies that the
Network Configuration:
- Assigns the service to the default Docker network (
default
).
- Assigns the service to the default Docker network (
Environment Variables:
Loads environment variables from the
.env
file.Sets the
NODE_ENV
environment variable.
Command Configuration:
- Runs the application using the command
['node', 'dist/src/main.js']
.
- Runs the application using the command
Healthcheck Configuration:
- Defines a healthcheck to ensure the service is healthy using a curl command to check the
/health
endpoint.
- Defines a healthcheck to ensure the service is healthy using a curl command to check the
database
Service:
Image Configuration:
- Pulls the
postgres:15.2-alpine
image from Docker Hub.
- Pulls the
Container Restart Policy:
- Configures the service to restart always.
Port Mapping:
- Maps the host's
5432
to the container's5432
for accessing the PostgreSQL database.
- Maps the host's
Environment Variables:
- Sets environment variables for PostgreSQL user, password, and database name.
Volume Configuration:
- Mounts a named volume (
pgdata
) to/var/lib/postgresql/data
for persistent storage.
- Mounts a named volume (
Healthcheck Configuration:
- Defines a healthcheck using the
pg_isready
command to check the availability of the PostgreSQL database.
- Defines a healthcheck using the
nginx
Service:
Image Configuration:
- Pulls the
nginx:1.25.2
image from Docker Hub.
- Pulls the
Port Mapping:
- Maps the host's
443
to the container's443
for accessing the Nginx service.
- Maps the host's
Dependency on
app
Service:- Specifies that the
nginx
service depends on theapp
service and will start once theapp
service is started.
- Specifies that the
Healthcheck Configuration:
- Defines a healthcheck by checking the status of the Nginx service using the
service nginx status
command.
- Defines a healthcheck by checking the status of the Nginx service using the
Environment Variables:
- Passes
APP_PORT
andDOMAIN
environment variables to the Nginx service.
- Passes
Volume Configuration:
Mounts the
default.conf.template
file as a read-only template for Nginx configuration.Mounts host directories for Nginx logs and SSL certificates.
Networks Section:
- Defines a Docker network named
${NETWORK_NAME}
(variable substitution) and assigns it as the default network for the services.
Volumes Section:
- Defines an external volume named
pgdata
, which is used for persisting PostgreSQL data.
This Docker Compose file sets up three services (app
, database
, and nginx
) with defined configurations for each. It incorporates health checks to ensure the services' availability and dependencies between services. The use of named volumes and environment variable substitution enhances configurability and maintainability. The overall setup is geared towards deploying a Nest.js application with a PostgreSQL database and Nginx as a reverse proxy.
deploy/default.conf.template file
include mime.types;
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name $DOMAIN;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
charset utf-8;
location / {
proxy_pass http://app:$APP_PORT;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
ssl_certificate /etc/ssl/$DOMAIN/certificate.crt;
ssl_certificate_key /etc/ssl/$DOMAIN/key.key;
}
server {
listen 80;
listen [::]:80;
server_name $DOMAIN;
return 302 https://$server_name$request_uri;
}
SSL Configuration (HTTPS):
Listen on Port 443 (HTTPS):
listen 443 ssl;
andlisten [::]:443 ssl;
specify that NGINX should listen on port 443 for secure SSL connections.
Server Name and Logging:
server_name $DOMAIN;
defines the server name using the$DOMAIN
variable.error_log
andaccess_log
directives set the paths for error and access logs.
Character Set:
charset utf-8;
declares the character set for encoding.
Proxy Configuration:
location / {...}
block configures the reverse proxy settings.proxy_pass
http://app:$APP_PORT
;
specifies the backend server's address and port.proxy_http_version 1.1;
sets the HTTP version for proxy connections.proxy_set_header
directives configure headers for the proxy request.proxy_cache_bypass $http_upgrade;
ensures WebSocket connections work correctly.
SSL Certificate and Key:
ssl_certificate
andssl_certificate_key
directives specify the paths to the SSL certificate and private key, respectively. The paths are based on the$DOMAIN
variable.
HTTP to HTTPS Redirect Configuration:
Listen on Port 80 (HTTP):
listen 80;
andlisten [::]:80;
specify that NGINX should listen on port 80 for regular HTTP connections.
Server Name and Redirect:
server_name $DOMAIN;
defines the server name for the HTTP block.return 302
https://$server_name$request_uri
;
returns a temporary (302) redirect to the equivalent HTTPS URL.
Variable Usage:
- The usage of variables like
$DOMAIN
and$APP_PORT
allows for dynamic configuration based on the values provided in the environment.
deploy/nestjsapp.deploy.Dockerfile file
FROM node:18.17.1-alpine3.18
LABEL 'maintainer'='Demo/Application'
WORKDIR /app
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#update the system
RUN apk update
#install nestjs cli
RUN yarn global add @nestjs/cli@10.1.12
Base Image:
FROM node:18.17.1-alpine3.18
: Specifies the base image for the Docker image, using Node.js version 18.17.1 on Alpine Linux version 3.18.
Metadata:
LABEL 'maintainer'='Demo/Application'
: Adds a metadata label to the image, specifying the maintainer of the Dockerfile.
Working Directory:
WORKDIR /app
: Sets the working directory inside the container to/app
. This will be the default directory for subsequent commands.
Timezone Configuration:
ENV TZ=UTC
: Sets theTZ
environment variable to UTC, which is then used for configuring the timezone.RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
: Configures the timezone in the container based on the value of theTZ
environment variable.
System Update:
RUN apk update
: Updates the package index of the Alpine Linux distribution. This step ensures that the package manager (apk
) has the latest information about available packages.
NestJS CLI Installation:
RUN yarn global add @nestjs/cli@10.1.12
: Installs the NestJS Command Line Interface (CLI) globally using the Yarn package manager. The specified version is 10.1.12.
deploy/migrate_db.sh file
docker compose -f 'deploy/docker-compose-deploy.yaml' -p 'app_deploy' run app yarn prisma migrate deploy
deploy/reboot_app.sh file
docker compose -f 'deploy/docker-compose-deploy.yaml' -p 'app_deploy' restart app
deploy/start_containers.sh file
docker compose -f 'deploy/docker-compose-deploy.yaml' -p 'app_deploy' up -d
Finally, the .env file (in the root of the deployment folder)
NETWORK_NAME='nestjs'
POSTGRES_USER=postgres
POSTGRES_PASSWORD=test_pw
POSTGRES_DATABASE=demo1
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DATABASE}?schema=public"
APP_PORT=3000
DOMAIN="your_domain.com"
Summary
This blog post introduces a comprehensive guide to streamline the deployment of NestJS backends through a Continuous Integration/Continuous Deployment (CI/CD) pipeline, utilizing CircleCI and AWS EC2. The significance of CI/CD is emphasized, providing developers with a seamless and automated process for testing, integrating, and deploying code changes.
The post highlights the importance of integration testing, covering both unit and end-to-end tests in the CI/CD pipeline. It stresses the benefits of early bug detection, enhanced code maintainability, and confidence in the application's functionality and stability.
Key components of the guide include the project structure, EC2 instance configuration, and server setup with Docker. The deployment workflow is explained, focusing on Docker image creation, dependency management, and efficient caching. Integration testing is facilitated through Docker containers, optimizing the testing process.
Docker Compose configurations are dissected, emphasizing the services for the NestJS app, PostgreSQL database, and Nginx reverse proxy. The Nginx configuration file is provided to illustrate SSL setup, reverse proxy configurations, and HTTP to HTTPS redirection.
The guide concludes by presenting deployment scripts and essential environment variables, facilitating a successful deployment process. Overall, the blog post equips developers with insights into the significance of CI/CD, showcasing its role in ensuring code quality, reliability, and a streamlined deployment experience for NestJS applications.