Deploy NestJS 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
- Login to Vultr Dashboard and click "Deploy New Server"
- Choose Server Type: Cloud Compute - Regular Performance
- Select Location: Choose the closest to your target audience
- Select Image: Ubuntu 22.04 LTS (recommended)
- Server Size: Start with $6/month (1 vCPU, 1GB RAM) - can scale later
- 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! 🚀