Deploy NestJS to Vultr VPS Using GitHub Actions

NestJS, DevOps|AUGUST 13, 2025|0 VIEWS
Complete guide to setting up automated deployment pipeline for NestJS applications to Vultr VPS using GitHub Actions

Introduction

Deploying a NestJS application to a VPS can be streamlined using GitHub Actions for continuous integration and deployment (CI/CD). In this comprehensive guide, we'll walk through setting up automated deployment from GitHub to a Vultr VPS, ensuring your NestJS application is automatically deployed whenever you push changes to your repository.

What You'll Learn

  • Setting up a Vultr VPS for hosting NestJS applications
  • Configuring GitHub Actions for automated deployment
  • Setting up PM2 for process management
  • Implementing zero-downtime deployments
  • Security best practices for VPS deployment

Prerequisites

Before we begin, make sure you have:

  • A NestJS application in a GitHub repository
  • A Vultr account (or any VPS provider)
  • Basic knowledge of Linux commands
  • Node.js and npm/yarn knowledge

Step 1: Setting Up Vultr VPS

Creating Your VPS Instance

  1. Login to Vultr Dashboard and click "Deploy New Server"
  2. Choose Server Type: Cloud Compute - Regular Performance
  3. Select Location: Choose the closest to your target audience
  4. Select Image: Ubuntu 22.04 LTS (recommended)
  5. Server Size: Start with $6/month (1 vCPU, 1GB RAM) - can scale later
  6. SSH Keys: Add your SSH public key for secure access

Initial Server Setup

Once your VPS is deployed, connect via SSH:

ssh root@your-server-ip

Update the system:

apt update && apt upgrade -y

Install Node.js (using NodeSource repository):

curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
apt-get install -y nodejs

Install PM2 globally:

npm install -g pm2

Install Nginx:

apt install nginx -y
systemctl start nginx
systemctl enable nginx

Create a non-root user:

adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Step 2: Preparing Your NestJS Application

Project Structure

Ensure your NestJS project has the following files:

package.json with proper scripts:

{
  "name": "nestjs-app",
  "version": "1.0.0",
  "scripts": {
    "build": "nest build",
    "start": "node dist/main",
    "start:dev": "nest start --watch",
    "start:prod": "node dist/main"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0"
  }
}

ecosystem.config.js for PM2:

module.exports = {
  apps: [
    {
      name: 'nestjs-app',
      script: 'dist/main.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_file: './logs/combined.log',
      time: true,
    },
  ],
};

Step 3: Setting Up GitHub Actions

Creating the Workflow File

Create .github/workflows/deploy.yml in your repository:

name: Deploy to Vultr VPS

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test

      - name: Build application
        run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Deploy to VPS
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            # Navigate to app directory
            cd /home/deploy/nestjs-app

            # Pull latest changes
            git pull origin main

            # Install dependencies
            npm ci --production

            # Build the application
            npm run build

            # Restart PM2 process
            pm2 restart ecosystem.config.js --env production

            # Save PM2 configuration
            pm2 save

Advanced Deployment Workflow

For more robust deployments, here's an enhanced workflow:

name: Advanced Deploy to Vultr VPS

on:
  push:
    branches: [main]

env:
  NODE_VERSION: '18'
  APP_NAME: 'nestjs-app'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint code
        run: npm run lint

      - name: Run tests
        run: npm run test:cov

      - name: Build application
        run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci --production

      - name: Build application
        run: npm run build

      - name: Create deployment package
        run: |
          tar -czf deploy.tar.gz dist/ node_modules/ package*.json ecosystem.config.js

      - name: Copy files to VPS
        uses: appleboy/scp-action@v0.1.4
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          key: ${{ secrets.VPS_SSH_KEY }}
          source: 'deploy.tar.gz'
          target: '/tmp/'

      - name: Deploy application
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            # Create backup of current deployment
            if [ -d "/home/deploy/${{ env.APP_NAME }}" ]; then
              sudo mv /home/deploy/${{ env.APP_NAME }} /home/deploy/${{ env.APP_NAME }}-backup-$(date +%Y%m%d-%H%M%S)
            fi

            # Create fresh deployment directory
            sudo mkdir -p /home/deploy/${{ env.APP_NAME }}
            cd /home/deploy/${{ env.APP_NAME }}

            # Extract deployment package
            sudo tar -xzf /tmp/deploy.tar.gz
            sudo chown -R deploy:deploy /home/deploy/${{ env.APP_NAME }}

            # Create logs directory
            mkdir -p logs

            # Start/restart application with PM2
            pm2 delete ${{ env.APP_NAME }} || true
            pm2 start ecosystem.config.js --env production
            pm2 save
            pm2 startup

            # Clean up
            rm /tmp/deploy.tar.gz

            # Remove old backups (keep last 3)
            sudo find /home/deploy/ -name "${{ env.APP_NAME }}-backup-*" -type d | sort -r | tail -n +4 | xargs sudo rm -rf

Step 4: Configuring GitHub Secrets

In your GitHub repository, go to Settings > Secrets and Variables > Actions, and add:

Required Secrets:

  • VPS_HOST: Your Vultr VPS IP address
  • VPS_USERNAME: deploy (the user we created)
  • VPS_SSH_KEY: Your private SSH key content

Getting Your SSH Key:

# Generate a new SSH key pair (if you don't have one)
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"

# Copy the private key content for GitHub secret
cat ~/.ssh/id_rsa

# Copy the public key to your VPS
ssh-copy-id deploy@your-server-ip

Step 5: Setting Up Nginx Reverse Proxy

Create an Nginx configuration for your NestJS app:

sudo nano /etc/nginx/sites-available/nestjs-app

Add the following configuration:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/nestjs-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Step 6: SSL Certificate with Let's Encrypt

Install Certbot and get SSL certificate:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

The Nginx configuration will be automatically updated for HTTPS.

Step 7: Environment Variables Management

Server-side Environment Setup:

# Create environment file
sudo nano /home/deploy/nestjs-app/.env.production

Add your production environment variables:

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-super-secret-jwt-key
REDIS_URL=redis://localhost:6379

Update ecosystem.config.js to use env file:

module.exports = {
  apps: [
    {
      name: 'nestjs-app',
      script: 'dist/main.js',
      instances: 'max',
      exec_mode: 'cluster',
      env_file: '.env.production',
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_file: './logs/combined.log',
      time: true,
    },
  ],
};

Step 8: Database Setup (PostgreSQL Example)

Install PostgreSQL:

sudo apt install postgresql postgresql-contrib -y
sudo systemctl start postgresql
sudo systemctl enable postgresql

Create database and user:

sudo -u postgres psql
CREATE DATABASE nestjs_app;
CREATE USER nestjs_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE nestjs_app TO nestjs_user;
\q

Step 9: Monitoring and Logging

PM2 Monitoring:

# Monitor processes
pm2 monit

# View logs
pm2 logs nestjs-app

# Restart app
pm2 restart nestjs-app

# Show app info
pm2 info nestjs-app

Setup Log Rotation:

sudo nano /etc/logrotate.d/nestjs-app
/home/deploy/nestjs-app/logs/*.log {
    daily
    missingok
    rotate 14
    compress
    notifempty
    create 0640 deploy deploy
    postrotate
        pm2 reloadLogs
    endscript
}

Step 10: Security Hardening

Firewall Configuration:

sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw status

Fail2Ban for SSH Protection:

sudo apt install fail2ban -y
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

Regular Updates:

Add to crontab for automatic security updates:

sudo crontab -e

Add:

0 2 * * * apt update && apt upgrade -y

Troubleshooting Common Issues

1. Deployment Fails

Check GitHub Actions logs for specific error messages:

# On VPS, check if directory exists
ls -la /home/deploy/

# Check PM2 status
pm2 status

# Check application logs
pm2 logs nestjs-app

2. Application Won't Start

# Check Node.js version
node --version

# Verify build files exist
ls -la /home/deploy/nestjs-app/dist/

# Check environment variables
pm2 env nestjs-app

3. Nginx Issues

# Test Nginx configuration
sudo nginx -t

# Check Nginx status
sudo systemctl status nginx

# View Nginx logs
sudo tail -f /var/log/nginx/error.log

4. Database Connection Issues

# Test PostgreSQL connection
psql -h localhost -U nestjs_user -d nestjs_app

# Check if PostgreSQL is running
sudo systemctl status postgresql

Conclusion

You now have a complete CI/CD pipeline that automatically deploys your NestJS application to Vultr VPS using GitHub Actions. This setup provides:

Automated Testing: Every push triggers tests before deployment
Zero-Downtime Deployment: PM2 cluster mode ensures availability
Security: SSL certificates, firewall, and fail2ban protection
Monitoring: PM2 process monitoring and log management

Key Benefits:

  • Fast Deployments: Changes go live automatically after tests pass
  • Reliability: Backup and rollback strategies included
  • Cost-Effective: Vultr VPS provides excellent value for hosting
  • Professional Setup: Production-ready configuration with best practices

Remember to regularly update your dependencies, monitor your application performance, and backup your database. As your application grows, you can easily scale by adding more VPS instances or upgrading your current server resources.

Happy deploying! 🚀