Home

Automating NestJS Deployment on Digital Ocean with GitHub Actions

The backend for my chat application is built using NestJS and TypeScript Firestore for data persistence and WebSockets for real-time communication. This setup ensures that users can find and connect with participants seamlessly, receive instant message notifications when online, and access new conversations upon re-entering the app after being offline. I deployed it on a DigitalOcean droplet and added continuous integration and deployment managed via GitHub Actions for smooth updates and maintenance.

Basically to setup the Node.js app on the Digital Ocean I did the following

  • I bought the cheapest droplet with ubuntu and domain name on namecheap
  • Created ssh without pathphrase to access droplet, later it will be used on github actions for acccess
  • Installed required packages node, npm , nginx with apt and process manager pm2 with npm on ubuntu system of the droplet
  • Used pm2 to start builded version of the app

My goal was to automate the update process of the app after pushing changes to the main branch, eliminating the need for manual pulls, rebuilds, and PM2 restarts. To achieve this, I used GitHub Actions to run a script that handles these tasks automatically.

The script, named deploy.sh, requires environment variables, which I added to GitHub's "Repository secrets" under "Actions secrets and variables."

#!/bin/bash

# Check if required environment variables are set
if [ -z "$REMOTE_USER" ] || [ -z "$REMOTE_HOST" ] || [ -z "$REMOTE_DIR" ] || [ -z "$PM2_PROCESS_NAME" ]; then
  echo "One or more environment variables are missing."
  exit 1
fi

# Use the environment variables to perform operations
ssh ${REMOTE_USER}@${REMOTE_HOST} << EOF
  cd ${REMOTE_DIR}
  pm2 stop ${PM2_PROCESS_NAME}
  git pull origin main
  npm run build
  pm2 start ${PM2_PROCESS_NAME}
EOF

Then I created a workflow to run this script and called it deploy.yml.

name: Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
        run: |
          # Ensure .ssh directory exists
          mkdir -p ~/.ssh

          # Save SSH private key
          echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa

          # Start the SSH agent and add the private key
          eval $(ssh-agent -s)
          ssh-add ~/.ssh/id_rsa

          # Add remote host to known_hosts
          ssh-keyscan -H $REMOTE_HOST >> ~/.ssh/known_hosts

      - name: Grant execute permission to deploy.sh
        run: chmod +x ./deploy.sh

      - name: Run deployment script
        env:
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_DIR: ${{ secrets.REMOTE_DIR }}
          PM2_PROCESS_NAME: ${{ secrets.PM2_PROCESS_NAME }}
        run: ./deploy.sh

So that's it—after I pushed my changes, I could see them deployed. Here is the GitHub repo that uses these files chat-backend-app