Deploy in 3 Simple Steps
Your blue-green deployment is already configured. This guide covers your everyday deployment workflow.
Run build script on your machine
Transfer to server via SCP/SFTP
Run automated deployment
🎯 Your Server Details
Enter your server details to customize all commands. Your values will appear in green highlights .
Complete Deployment Workflow
Step 1: Build Locally
# Navigate to your project
cd C:\www\your-project
# Clean previous build
pnpm dlx rimraf .nuxt .output node_modules
# Set environment and build
$env:NETLIFY_BLOBS = 'false'
pnpm install
pnpm build
# Prepare deployment folder
powershell -ExecutionPolicy Bypass -File prepare-deploy.ps1
# Navigate to your project
cd ~/projects/your-project
# Clean previous build
pnpm dlx rimraf .nuxt .output node_modules
# Set environment and build
export NETLIFY_BLOBS=false
pnpm install
pnpm build
# Prepare deployment folder
./prepare-deploy.sh
# Navigate to your project
cd ~/projects/your-project
# Clean previous build
pnpm dlx rimraf .nuxt .output node_modules
# Set environment and build
export NETLIFY_BLOBS=false
pnpm install
pnpm build
# Prepare deployment folder
./prepare-deploy.sh
Step 2: Upload to Server
Option A: Using SCP (WSL/Git Bash/PowerShell)
# Generate timestamp
$VERSION = Get-Date -Format "yyyyMMdd_HHmmss"
echo $VERSION
# Upload deployment folder
scp -r C:\www\your-project\deploy-folder\* {{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}}:/tmp/app-release-$VERSION/
Option B: Using Bitvise/FileZilla/WinSCP (GUI)
-
Connect to:
{{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}} -
Create temporary folder:
/tmp/app-release-YYYYMMDD_HHMMSSOR/var/tmp/app-release-YYYYMMDD_HHMMSS(use current timestamp) -
Upload all contents from (select all and drag to get content from subfolders):
C:\www\your-project\deploy-folder\*
/var/tmp/
instead of
/tmp/
. The deployment script checks both locations automatically. If you
manually specify a path, use either location - both work!
Step 3: Deploy on Server
# SSH into your server
ssh {{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}}
# Navigate to your domain directory (IMPORTANT!)
cd /var/www/{{deploy.domain || 'your-domain.com'}}
# Run deployment script
./scripts/deploy-zero-downtime.sh
/var/www/your-domain.com/
, not from the
scripts/
subdirectory. This ensures the script knows which app to deploy.
Quick Reference Commands
🚀 Deploy
./deploy-zero-downtime.sh
🔙 Rollback
./rollback.sh
📊 Check Status
# View active releases
ls -lt /var/www/{{deploy.domain || 'your-domain.com'}}/releases/
# See current version
readlink /var/www/{{deploy.domain || 'your-domain.com'}}/current
# Check PM2 status
pm2 list
# View logs
pm2 logs {{deploy.appname || 'app-name'}} --lines 50
🔍 Health Check
# Test production endpoint
curl -I http://127.0.0.1:{{deploy.prodport || '3101'}}/
# Test from outside
curl -I https://{{deploy.domain || 'your-domain.com'}}/
🗄️ Database Migrations
# Check migration status
cd /var/www/{{deploy.domain || 'your-domain.com'}}/current
pnpm dlx prisma migrate status
# Apply migrations
pnpm dlx prisma migrate deploy
# Quick sync (dev/staging)
pnpm dlx prisma db push
# Regenerate Prisma client
pnpm dlx prisma generate
# Restart after DB changes
pm2 restart {{deploy.appname || 'app-name'}} --update-env
Manual Deployment (Without Script)
If you prefer manual control or the script isn't available:
# Set version (auto-detect latest upload)
VERSION=$(ls -t /tmp/app-release-* | head -1 | sed 's|/tmp/app-release-||')
DEPLOY_DIR="/var/www/{{deploy.domain || 'your-domain.com'}}/releases/$VERSION"
# Move files to releases (including hidden files like .output and .env)
mkdir -p "$DEPLOY_DIR"
shopt -s dotglob nullglob
mv "/tmp/app-release-$VERSION"/* "$DEPLOY_DIR/"
shopt -u dotglob nullglob
rm -rf "/tmp/app-release-$VERSION"
# Install dependencies
cd "$DEPLOY_DIR"
nvm use 20.18.1
pnpm install --prod --frozen-lockfile
pnpm dlx prisma generate
pnpm dlx prisma db push || true
pnpm run preflight || true
pnpm run preflight:db || true
mkdir -p logs && chmod 775 logs
# Link shared resources
ln -sf /var/www/{{deploy.domain || 'your-domain.com'}}/shared/.env "$DEPLOY_DIR/.env"
ln -sf /var/www/{{deploy.domain || 'your-domain.com'}}/shared/db.sqlite "$DEPLOY_DIR/prisma/db.sqlite" 2>/dev/null || true
# Test on alternate port
PORT={{deploy.testport || '3102'}} node .output/server/index.mjs &
TEST_PID=$!
sleep 5
if curl -f -I http://127.0.0.1:{{deploy.testport || '3102'}}/ > /dev/null 2>&1; then
echo "✅ Health check passed"
kill $TEST_PID
else
echo "❌ Health check failed - ABORTING"
kill $TEST_PID 2>/dev/null || true
exit 1
fi
# Switch to new release
ln -sfn "$DEPLOY_DIR" /var/www/{{deploy.domain || 'your-domain.com'}}/current
pm2 restart {{deploy.appname || 'app-name'}} --update-env
pm2 save
# Verify production
sleep 2
curl -I http://127.0.0.1:{{deploy.prodport || '3101'}}/
pm2 logs {{deploy.appname || 'app-name'}} --lines 30
# Clean old releases (keep last 3)
cd /var/www/{{deploy.domain || 'your-domain.com'}}/releases
ls -t | tail -n +4 | xargs -r rm -rf
echo "✅ Deployment complete!"
Troubleshooting Quick Fixes
🚨 Changes Not Appearing After Deployment
Symptom: You deployed successfully but your code changes aren't visible on the production site, even in incognito mode.
- Nuxt cache issue: Your build used cached files instead of your latest changes
- Service Worker caching: Old PWA service worker is serving cached content
- Browser cache: Browser cached assets despite deployment
Solution 1: Clean Build (Most Common Fix)
# On your local machine, remove Nuxt cache folders
Remove-Item -Path ".nuxt" -Recurse -Force
Remove-Item -Path ".output" -Recurse -Force
Remove-Item -Path "node_modules\.vite" -Recurse -Force
# Clean rebuild
pnpm build
# Re-prepare deployment
powershell -ExecutionPolicy Bypass -File prepare-deploy.ps1
# Deploy again (upload and run deployment script)
Solution 2: Clear Service Worker
# If you previously had PWA enabled, clear service workers
# In Chrome/Edge: F12 → Application → Service Workers → Unregister
# In Firefox: about:serviceworkers → Unregister
Solution 3: Verify Build Timestamp
# Check when your code was last modified
Get-ChildItem -Path ".\layers\marketing\app\pages\index.vue" | Select-Object LastWriteTime
# Check when .output was created
Get-ChildItem -Path ".\.output" | Select-Object LastWriteTime
# If .output is OLDER than your code changes, you need a clean build!
Deployment Script Fails
# Check uploaded files exist (check both locations!)
ls -la /tmp/app-release-*
ls -la /var/tmp/app-release-*
# View script output for errors
./scripts/deploy-zero-downtime.sh 2>&1 | tee deploy.log
# Make script executable if needed
chmod +x ./scripts/deploy-zero-downtime.sh
Health Check Fails
# Test new release manually
cd /var/www/{{deploy.domain || 'your-domain.com'}}/releases/[VERSION]
PORT={{deploy.testport || '3102'}} node .output/server/index.mjs
# Check for missing .env
ls -la .env
cat .env
# Verify database link
ls -la prisma/db.sqlite
PM2 Won't Restart
# Check PM2 status
pm2 list
pm2 describe {{deploy.appname || 'app-name'}}
# Force restart
pm2 restart {{deploy.appname || 'app-name'}}
# Delete and recreate if stuck
pm2 delete {{deploy.appname || 'app-name'}}
cd /var/www/{{deploy.domain || 'your-domain.com'}}/current
pm2 start ecosystem.config.cjs --env production
pm2 save
Database Errors After Deploy
# Schema mismatch - sync it
cd /var/www/{{deploy.domain || 'your-domain.com'}}/current
pnpm dlx prisma db push
pnpm dlx prisma generate
pm2 restart {{deploy.appname || 'app-name'}}
# View Prisma errors in logs
pm2 logs {{deploy.appname || 'app-name'}} | grep -i prisma
Emergency Rollback
# Quick rollback to previous version
CURRENT=$(readlink /var/www/{{deploy.domain || 'your-domain.com'}}/current | sed 's|.*/||')
PREVIOUS=$(ls -t /var/www/{{deploy.domain || 'your-domain.com'}}/releases/ | grep -v "^${CURRENT}$" | head -1)
ln -sfn "/var/www/{{deploy.domain || 'your-domain.com'}}/releases/$PREVIOUS" /var/www/{{deploy.domain || 'your-domain.com'}}/current
pm2 restart {{deploy.appname || 'app-name'}} --update-env
echo "Rolled back to: $PREVIOUS"
Build Errors
"Cannot access 'sessionHooks' before initialization"
This error occurs when Nuxt Auth Utils exports
sessionHooks
in the wrong order in the bundled nitro.mjs file.
# Check if you have the patch-sessionhooks module
ls modules/patch-sessionhooks.ts
# If not, this module should automatically fix the issue during build
# If error persists after rebuild, the module may need updating
PWA Build Failures
If you encounter PWA-related build errors and don't need PWA features:
# In nuxt.config.ts, comment out the PWA module:
modules: [
"@nuxt/eslint",
"@nuxt/image",
"@nuxt/content",
"@nuxt/ui",
"./modules/patch-sessionhooks",
// "@vite-pwa/nuxt", // DISABLED - causing build issues
],
# Clean rebuild after disabling
Remove-Item -Path ".nuxt" -Recurse -Force
Remove-Item -Path ".output" -Recurse -Force
pnpm build
Empty .output/server Folder
If your
.output/server/
folder is empty after build, the build actually failed but may not have shown a clear error.
# Check for build errors in full output
pnpm build 2>&1 | tee build.log
# Common causes:
# - PWA module errors (disable PWA if not needed)
# - TypeScript errors (run: pnpm typecheck)
# - Missing dependencies (run: pnpm install)
# - Environment variable issues
# Verify server output was created
Test-Path .output\server\index.mjs
# Should return: True
Pre-Deployment Checklist
- ✅ Code tested locally with production environment
- ✅ Build completes without errors
- ✅ Clean build performed if code changes aren't appearing (remove .nuxt, .output, and node_modules/.vite folders)
- ✅ Verify .output/server/index.mjs file exists and has recent timestamp
- ✅ Database migrations created (if schema changed)
- ✅ Environment variables up to date in shared/.env
- ✅ Dependencies updated and tested
- ✅ Check current disk space on server
Post-Deployment Verification
- 🔍 Check production endpoint responds
- 🔍 Review PM2 logs for errors
- 🔍 Test critical user flows
- 🔍 Verify database migrations applied
- 🔍 Monitor performance metrics
Deployment Best Practices
- Deploy during low-traffic hours initially
- Keep PM2 logs open during deploy
- Test rollback procedure periodically
- Backup database before major changes
- Skip the health check step
- Delete old releases manually (script handles it)
- Modify files in current/ (always in releases/)
- Forget to backup before migrations
Common Questions
Why does my PM2 log show "CSP Disabled"?
This is normal and expected. Content Security Policy (CSP) is an optional security feature
that is disabled by default. If you want to enable it, add
CSP_ENABLED=true
to your
/var/www/your-domain.com/shared/.env
file and restart PM2.
How do I know if I need a clean build?
You need a clean build when:
- Your code changes don't appear after deployment
-
The
.outputfolder timestamp is older than your last code change - You switched branches or merged major changes
- You're getting strange build errors that disappear after cleaning
What if I have multiple domains on the same server?
The deployment scripts are domain-aware! Just make sure you're in the correct domain directory before running the script:
# For domain1.com
cd /var/www/domain1.com
./scripts/deploy-zero-downtime.sh
# For domain2.com
cd /var/www/domain2.com
./scripts/deploy-zero-downtime.sh
Need Help?
- Initial Setup: Complete Zero-Downtime Setup Guide
- Deployment Scripts: Check the main guide for deploy-zero-downtime.sh and rollback.sh
- PM2 Configuration: Ensure ecosystem.config.cjs has correct cwd path
- Database Issues: Review Prisma migration section in main guide