Deploying Next.js with MongoDB, Prisma, TailwindCSS, and shadcn/ui to VPS using GitHub Actions

Overview
This guide provides a complete setup for deploying a Next.js application with MongoDB, Prisma, TailwindCSS, and shadcn/ui to a VPS server using GitHub Actions for continuous deployment.
Prerequisites
- VPS server with Ubuntu 20.04+ (DigitalOcean, Linode, AWS EC2, etc.)
- GitHub repository with your Next.js project
- SSH access to your VPS
- Domain name (optional)
VPS Server Setup
1. Initial Server Configuration
# Update systemsudo apt update && sudo apt upgrade -y# Install Node.jscurl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -sudo apt-get install -y nodejs# Install PM2 for process managementsudo npm install -g pm2# Install MongoDBwget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.listsudo apt updatesudo apt install -y mongodb-org# Start MongoDBsudo systemctl start mongodsudo systemctl enable mongod# Install Nginxsudo apt install -y nginx
2. Create Deployment User
# Create a new user for deploymentssudo adduser deployersudo usermod -aG sudo deployer# Switch to deployer usersu - deployer
3. SSH Key Setup
Generate SSH key pair on your VPS:
ssh-keygen -t ed25519 -C "deployer@your-domain.com"cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keyschmod 600 ~/.ssh/authorized_keys
GitHub Repository Configuration
1. Add Secrets to GitHub
Go to your repository Settings → Secrets and variables → Actions and add:
- : Your VPS IP address or domainVPS_HOST
- :VPS_USERNAMEdeployer
- : The private SSH key from your VPSVPS_SSH_KEY
- :NODE_ENVproduction
- : Your MongoDB connection stringDATABASE_URL
2. Environment Variables
Create in your project:
.env.production
env
NODE_ENV=productionDATABASE_URL=mongodb://localhost:27017/your-database-nameNEXTAUTH_SECRET=your-nextauth-secretNEXTAUTH_URL=https://your-domain.com
GitHub Actions Workflows
1. Main Deployment Workflow
Create :
.github/workflows/deploy.yml
yaml
name: Deploy to VPSon:push:branches: [ main, master ]pull_request:branches: [ main, master ]jobs:deploy:runs-on: ubuntu-latestif: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'steps:- name: Checkout codeuses: actions/checkout@v4- name: Setup Node.jsuses: actions/setup-node@v4with:node-version: '18'cache: 'npm'- name: Install dependenciesrun: npm ci- name: Generate Prisma clientrun: npx prisma generate- name: Run testsrun: npm run test- name: Build applicationrun: npm run buildenv:DATABASE_URL: ${{ secrets.DATABASE_URL }}NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }}- name: Deploy to VPSuses: appleboy/ssh-action@v1.0.3with:host: ${{ secrets.VPS_HOST }}username: ${{ secrets.VPS_USERNAME }}key: ${{ secrets.VPS_SSH_KEY }}script: |cd /home/deployer/appgit pull origin mainnpm ci --only=productionnpx prisma generatenpx prisma db pushnpm run buildpm2 restart nextjs-app || pm2 start npm --name "nextjs-app" -- start
2. Comprehensive Deployment with Rollback
Create :
.github/workflows/deploy-advanced.yml
yaml
name: Advanced Deploy to VPSon:push:branches: [ main, master ]workflow_dispatch:env:NODE_VERSION: '18'APP_DIR: '/home/deployer/app'APP_NAME: 'nextjs-app'jobs:test:runs-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkout@v4- name: Setup Node.jsuses: actions/setup-node@v4with:node-version: ${{ env.NODE_VERSION }}cache: 'npm'- name: Install dependenciesrun: npm ci- name: Generate Prisma clientrun: npx prisma generate- name: Run type checkrun: npx tsc --noEmit- name: Run testsrun: npm run test- name: Run lintingrun: npm run lint- name: Build applicationrun: npm run buildenv:DATABASE_URL: ${{ secrets.DATABASE_URL }}NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }}deploy:runs-on: ubuntu-latestneeds: testif: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'steps:- name: Checkout codeuses: actions/checkout@v4- name: Setup deployment environmentrun: |mkdir -p deploycp -r ./* deploy/cd deploy && tar -czf ../app.tar.gz .- name: Create deployment scriptrun: |cat > deploy.sh << 'EOF'#!/bin/bashset -eAPP_DIR="/home/deployer/app"BACKUP_DIR="/home/deployer/backups"TIMESTAMP=$(date +%Y%m%d_%H%M%S)# Create backupif [ -d "$APP_DIR" ]; thentar -czf "$BACKUP_DIR/backup_$TIMESTAMP.tar.gz" -C /home/deployer appfi# Extract new versiontar -xzf app.tar.gz -C $APP_DIR# Install dependenciescd $APP_DIRnpm ci --only=production# Setup databasenpx prisma generatenpx prisma db push# Build applicationnpm run build# Restart applicationpm2 describe nextjs-app > /dev/null 2>&1 && pm2 reload nextjs-app || pm2 start npm --name "nextjs-app" -- start# Save PM2 configurationpm2 savepm2 startupEOFchmod +x deploy.sh- name: Copy files to VPSuses: appleboy/scp-action@v0.1.7with:host: ${{ secrets.VPS_HOST }}username: ${{ secrets.VPS_USERNAME }}key: ${{ secrets.VPS_SSH_KEY }}source: "app.tar.gz,deploy.sh"target: "/home/deployer/"strip_components: 1- name: Execute deployment scriptuses: appleboy/ssh-action@v1.0.3with:host: ${{ secrets.VPS_HOST }}username: ${{ secrets.VPS_USERNAME }}key: ${{ secrets.VPS_SSH_KEY }}script: |cd /home/deployerchmod +x deploy.shmkdir -p backups./deploy.shrm -f app.tar.gz deploy.sh- name: Verify deploymentuses: appleboy/ssh-action@v1.0.3with:host: ${{ secrets.VPS_HOST }}username: ${{ secrets.VPS_USERNAME }}key: ${{ secrets.VPS_SSH_KEY }}script: |sleep 10curl -f http://localhost:3000/api/health || exit 1notify:runs-on: ubuntu-latestneeds: deployif: always()steps:- name: Notify deployment statususes: 8398a7/action-slack@v3with:status: ${{ job.status }}text: Deployment ${{ job.status }} for ${{ github.repository }}env:SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
3. Database Migration Workflow
Create :
.github/workflows/migrate-db.yml
yaml
name: Database Migrationon:workflow_dispatch:push:branches: [ main, master ]paths:- 'prisma/**'jobs:migrate:runs-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkout@v4- name: Setup Node.jsuses: actions/setup-node@v4with:node-version: '18'- name: Install dependenciesrun: npm ci- name: Deploy database changesuses: appleboy/ssh-action@v1.0.3with:host: ${{ secrets.VPS_HOST }}username: ${{ secrets.VPS_USERNAME }}key: ${{ secrets.VPS_SSH_KEY }}script: |cd /home/deployer/appgit pull origin mainnpm cinpx prisma generatenpx prisma migrate deploynpx prisma db seed
VPS Application Setup Script
Create on your VPS:
setup-app.sh
bash
#!/bin/bashset -eAPP_DIR="/home/deployer/app"LOG_DIR="/home/deployer/logs"echo "Setting up Next.js application..."# Create directoriesmkdir -p $APP_DIR $LOG_DIR# Clone repository (initial setup)cd $APP_DIRif [ ! -d ".git" ]; thengit clone https://github.com/your-username/your-repo.git .fi# Install dependenciesnpm ci --only=production# Generate Prisma clientnpx prisma generate# Push database schemanpx prisma db push# Build applicationnpm run build# Create PM2 ecosystem filecat > ecosystem.config.js << 'EOF'module.exports = {apps: [{name: 'nextjs-app',script: 'npm',args: 'start',instances: 'max',exec_mode: 'cluster',env: {NODE_ENV: 'production',PORT: 3000},error_file: '/home/deployer/logs/err.log',out_file: '/home/deployer/logs/out.log',log_file: '/home/deployer/logs/combined.log',time: true}]}EOF# Start application with PM2pm2 start ecosystem.config.jspm2 savepm2 startupecho "Application setup completed!"
Nginx Configuration
Create :
/etc/nginx/sites-available/your-domain.com
nginx
server {listen 80;server_name your-domain.com www.your-domain.com;# Security headersadd_header X-Frame-Options "SAMEORIGIN" always;add_header X-XSS-Protection "1; mode=block" always;add_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "no-referrer-when-downgrade" always;add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;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;}# Cache static assetslocation /_next/static/ {proxy_cache STATIC;proxy_pass http://localhost:3000;proxy_ignore_headers Cache-Control;proxy_cache_valid 200 60d;}location /api/ {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_cache_bypass $http_upgrade;}}
Enable the site:
bash
sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginx
Monitoring and Logs
PM2 Monitoring
bash
# PM2 commandspm2 status # Check application statuspm2 logs nextjs-app # View application logspm2 monit # Monitor resourcespm2 restart nextjs-app # Restart application
Log Rotation
Create :
/etc/logrotate.d/nextjs-app
text
/home/deployer/logs/*.log {dailymissingokrotate 7compressdelaycompressnotifemptycopytruncate}
Troubleshooting
Common Issues
- Build failures: Check Node.js version compatibility
- Database connection issues: Verify MongoDB service is running
- Permission errors: Ensure deployer user has correct permissions
- Port conflicts: Check if port 3000 is available
Debugging Steps
bash
# Check application statuspm2 status# Check logspm2 logs nextjs-app --lines 100# Verify MongoDBsudo systemctl status mongodmongo --eval "db.adminCommand('ismaster')"# Check Nginxsudo nginx -tsudo systemctl status nginx# Verify networkcurl http://localhost:3000
Security Considerations
- Use strong SSH keys and disable password authentication
- Configure firewall (UFW) to allow only necessary ports
- Keep system and dependencies updated
- Use environment variables for sensitive data
- Implement rate limiting and DDoS protection
- Regular security audits and updates
Conclusion
This setup provides a robust, automated deployment pipeline for Next.js applications with MongoDB, Prisma, TailwindCSS, and shadcn/ui. The GitHub Actions workflows ensure consistent deployments with proper testing, building, and deployment steps.
The configuration includes:
- Automated testing and building
- Zero-downtime deployments
- Database migrations
- Comprehensive monitoring
- Security best practices
With this setup, you can focus on development while GitHub Actions handles the deployment process automatically.
1