Skip to main content

How To Deploy Next.js with PM2 and CircleCI

Prerequisite

How to Deploy Next.js with PM2

Set up Circle CI Config

  • Objective: Get a build passing on CircleCI.
  • Go to CircleCI.
  • Log in with GitHub.
  • Authorize CircleCI.
  • Go to Projects.
  • Click Set Up Project on your project.
  • Click on Fast.
  • Click on Set Up Project.
  • Select Node (Advanced).
  • Click Commit and Run.
  • The test should fail.
  • Check out the circleci-project-setup branch locally.
  • Add "test": "exit 0" to the scripts in package.json. This is just to get the tests to pass. Real tests can be added later.
  • Go back to the project in CircleCI. The build should be passing.
  • The config file should like like this:
version: 2.1

orbs:
node: circleci/node@4.7

jobs:
build-and-test:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Run tests
command: npm test

workflows:
sample:
jobs:
- build-and-test

Deploy to EC2 Instance

  • Objective: Successful build should trigger a deployment.

  • Base64 encode the key.pem.

    cat key.pem | base64 | clip
  • In CircleCI, go to Project Settings.

  • Go to Environment Variables.

  • Click Add Environment Variable.

  • Name the variable KEY_PEM.

  • Paste in the base64 encoded key as the value.

  • Change config.yml to look like this:

version: 2.1

orbs:
node: circleci/node@4.7

jobs:
build-and-test:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Run tests
command: npm test
deploy_to_production:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Create key.pem
command: |
echo $KEY_PEM | base64 -d -i > key.pem
chmod 400 key.pem
- run:
name: Add to known hosts
command: ssh-keyscan -H 54.200.60.31 >> ~/.ssh/known_hosts
- run:
name: Install pm2
command: sudo npm i -g pm2
- run:
name: Deploy to EC2
command: pm2 deploy production

workflows:
sample:
jobs:
- build-and-test
- deploy_to_production:
requires:
- build-and-test

  • Create a key.pem from the environment variable, add host to known hosts, install pm2, and deploy.
  • Job added to the workflow.
  • Push to git with git add . and git commit -m "update circleci config" and git push.
  • Verify that the build has succeeded and deployed to production.

Branch level job execution

  • Objective: Merges into staging should trigger a deployment to staging. Merges into main should trigger a deployment to production.
  • Change ecosystem.config.js to look like this:
module.exports = {
apps: [
{
script: "npm start",
},
],

deploy: {
production: {
key: "key.pem",
user: "ubuntu",
host: "54.200.60.31",
ref: "origin/main",
repo: "git@github.com:travisluong/fullstackbook-nextjs-pm2.git",
path: "/home/ubuntu",
"pre-deploy-local": "",
"post-deploy":
"source ~/.nvm/nvm.sh && npm install && npm run build && pm2 reload ecosystem.config.js --env production",
"pre-setup": "",
ssh_options: "ForwardAgent=yes",
},
staging: {
key: "key.pem",
user: "ubuntu",
host: "54.200.60.31",
ref: "origin/staging",
repo: "git@github.com:travisluong/fullstackbook-nextjs-pm2.git",
path: "/home/ubuntu",
"pre-deploy-local": "",
"post-deploy":
"source ~/.nvm/nvm.sh && npm install && npm run build && pm2 reload ecosystem.config.js --env staging",
"pre-setup": "",
ssh_options: "ForwardAgent=yes",
},
},
};

  • Staging deployment added.
  • Change config.yml to look like this:
version: 2.1

orbs:
node: circleci/node@4.7

jobs:
build-and-test:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Run tests
command: npm test
deploy_to_production:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Create key.pem
command: |
echo $KEY_PEM | base64 -d -i > key.pem
chmod 400 key.pem
- run:
name: Add to known hosts
command: ssh-keyscan -H 54.200.60.31 >> ~/.ssh/known_hosts
- run:
name: Install pm2
command: sudo npm i -g pm2
- run:
name: Deploy to EC2
command: pm2 deploy production
deploy_to_staging:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Create key.pem
command: |
echo $KEY_PEM | base64 -d -i > key.pem
chmod 400 key.pem
- run:
name: Add to known hosts
command: ssh-keyscan -H 54.200.60.31 >> ~/.ssh/known_hosts
- run:
name: Install pm2
command: sudo npm i -g pm2
- run:
name: Deploy to EC2
command: pm2 deploy staging
workflows:
build:
jobs:
- build-and-test:
filters:
branches:
ignore:
- main
- staging
production:
jobs:
- build-and-test:
filters: &filters-production
branches:
only: main
- deploy_to_production:
filters:
<<: *filters-production
requires:
- build-and-test
staging:
jobs:
- build-and-test:
filters: &filters-staging
branches:
only: staging
- deploy_to_staging:
filters:
<<: *filters-staging
requires:
- build-and-test

  • deploy_to_staging job added.
  • There are three workflows now.
    • build runs the build_and_test job for all branches except main and staging.
    • production runs the build_and_test job and the deploy_to_production job for only the main branch.
    • staging runs the build_and_test job and the deploy_to_staging job for only the staging branch.
  • The &filters-production is a yaml anchor. It is a way of re-using the configuration. For example, the nested properties are re-used anywhere <<: *filters-production is used.

Add manual approval

  • Objective: Add a button to trigger a deployment, instead of automatically deploying.
  • Change config.yml to look like this:
version: 2.1

orbs:
node: circleci/node@4.7

jobs:
build-and-test:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Run tests
command: npm test
deploy_to_production:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Create key.pem
command: |
echo $KEY_PEM | base64 -d -i > key.pem
chmod 400 key.pem
- run:
name: Add to known hosts
command: ssh-keyscan -H 54.200.60.31 >> ~/.ssh/known_hosts
- run:
name: Install pm2
command: sudo npm i -g pm2
- run:
name: Deploy to EC2
command: pm2 deploy production
deploy_to_staging:
docker:
- image: cimg/node:16.10
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Create key.pem
command: |
echo $KEY_PEM | base64 -d -i > key.pem
chmod 400 key.pem
- run:
name: Add to known hosts
command: ssh-keyscan -H 54.200.60.31 >> ~/.ssh/known_hosts
- run:
name: Install pm2
command: sudo npm i -g pm2
- run:
name: Deploy to EC2
command: pm2 deploy staging
workflows:
build:
jobs:
- build-and-test:
filters:
branches:
ignore:
- main
- staging
production:
jobs:
- build-and-test:
filters: &filters-production
branches:
only: main
- hold_for_approval:
type: approval
requires:
- build-and-test
- deploy_to_production:
filters:
<<: *filters-production
requires:
- build-and-test
- hold_for_approval
staging:
jobs:
- build-and-test:
filters: &filters-staging
branches:
only: staging
- hold_for_approval:
type: approval
requires:
- build-and-test
- deploy_to_staging:
filters:
<<: *filters-staging
requires:
- build-and-test
- hold_for_approval

  • The hold_for_approval job was added in both the production and staging workflow.

Reference