Deploy in 3 Simple Steps

Your blue-green deployment is already configured. This guide covers your everyday deployment workflow.

1
Build Locally

Run build script on your machine

2
Upload Files

Transfer to server via SCP/SFTP

3
Deploy Script

Run automated deployment

New to zero-downtime deployment? Check out the complete setup guide first.

🎯 Your Server Details

Enter your server details to customize all commands. Your values will appear in green highlights .

Enter your domain name without protocol
Enter application name without spaces
Enter your server username for SSH access
Enter IPv4 address of your server
Port for production application
Port for testing before deployment

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
What this does: Builds your Nuxt app and copies necessary files (output, public, package files, etc.) to the deploy-folder directory.

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)

  1. Connect to: {{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}}
  2. Create temporary folder: /tmp/app-release-YYYYMMDD_HHMMSS OR /var/tmp/app-release-YYYYMMDD_HHMMSS (use current timestamp)
  3. Upload all contents from (select all and drag to get content from subfolders): C:\www\your-project\deploy-folder\*
Important: Some SFTP clients (like Bitvise) may upload to /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
Directory Matters! The deployment script auto-detects your domain from the current directory. Always run it from /var/www/your-domain.com/ , not from the scripts/ subdirectory. This ensures the script knows which app to deploy.
That's it! The automated script handles everything: installing dependencies, testing, switching symlinks, reloading PM2, and cleaning old releases.

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.

Common Causes:
  1. Nuxt cache issue: Your build used cached files instead of your latest changes
  2. Service Worker caching: Old PWA service worker is serving cached content
  3. 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"

Error: ReferenceError: 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

Error: Cannot read properties of undefined (reading 'Symbol(ProxyTarget)')

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

Post-Deployment Verification

Deployment Best Practices

✅ Do
  • Deploy during low-traffic hours initially
  • Keep PM2 logs open during deploy
  • Test rollback procedure periodically
  • Backup database before major changes
❌ Don't
  • 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:

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?