Web Deployment Automation — From Git Push to Production in One Command
Modify code, commit, SSH into the server, pull, build, restart the process manager... repeating this manually every deployment wastes time and invites human error. A single shell script automates this entire sequence — deployment becomes one command. No CI/CD tools required. This guide covers building a reliable deployment pipeline with shell scripts alone: the deployment script, rollback strategy, environment variable management, and multi-server synchronization.
Deployment Pipeline Design
The core principle of a deployment pipeline is repeatable automation. Manual steps produce mistakes, and mistakes cause outages. Every deployment step must be defined and scripted.
The deployment pipeline flow:
Step 1: Modify code on development machine, git commit, git push.
Step 2: Production server runs git pull (or triggered automatically via webhook).
Step 3: Install dependencies with npm ci (clean install).
Step 4: Build the application — npm run build (generates Next.js .next folder).
Step 5: PM2 reload (zero-downtime restart).
Step 6: Verify deployment result and send notification.
Use npm ci instead of npm install. npm ci installs exact package versions based on package-lock.json. Unlike npm install, it never modifies the lock file, ensuring development and production environments always use identical dependency versions.
Writing the Deployment Shell Script
The script below is based on a real production deployment setup. It halts immediately on any error and logs the success or failure of each step.
#!/bin/bash
set -e # Stop immediately on error
APP_DIR="/home/deploy/my-app"
BACKUP_DIR="/home/deploy/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "=== Deployment started: $TIMESTAMP ==="
# 1. Backup previous build
echo "[1/5] Backing up previous build..."
mkdir -p $BACKUP_DIR
if [ -d "$APP_DIR/.next" ]; then
cp -r "$APP_DIR/.next" "$BACKUP_DIR/.next_$TIMESTAMP"
fi
# 2. Pull latest code
echo "[2/5] Git pull..."
cd $APP_DIR
git pull origin main
# 3. Install dependencies
echo "[3/5] Installing packages..."
npm ci
# 4. Build
echo "[4/5] Building..."
npm run build
# 5. Restart PM2 (zero-downtime)
echo "[5/5] PM2 reload..."
pm2 reload my-app
echo "=== Deployment complete: $(date +%H:%M:%S) ==="
echo "Duration: ${SECONDS}s"# Make executable and run
chmod +x deploy.sh
# Run deployment
./deploy.sh
# Or deploy remotely via SSH
ssh deploy-server "cd /home/deploy/my-app && ./deploy.sh"Rollback Strategy — Instant Recovery When Deployment Fails
When a deployment fails, you need to revert to the previous version fast. Keeping build artifacts as backups and preparing a rollback script in advance minimizes downtime during incidents.
#!/bin/bash
set -e
APP_DIR="/home/deploy/my-app"
BACKUP_DIR="/home/deploy/backups"
# Find the latest backup
LATEST_BACKUP=$(ls -t $BACKUP_DIR/.next_* -d 2>/dev/null | head -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "No backup files found."
exit 1
fi
echo "Rolling back to: $LATEST_BACKUP"
# Replace .next directory
rm -rf "$APP_DIR/.next"
cp -r "$LATEST_BACKUP" "$APP_DIR/.next"
# Restart PM2
pm2 reload my-app
echo "Rollback complete: $(date +%H:%M:%S)"| Rollback Method | Recovery Time | Advantage | Disadvantage |
|---|---|---|---|
| Build backup swap | 5 seconds | No rebuild needed | Increased disk usage |
| git revert + rebuild | 2-5 minutes | Git history tracking | Build time required |
| Docker image swap | 10-30 seconds | Complete environment restoration | Docker infrastructure required |
Keep at least 3 backups. You should be able to roll back not just to the previous deployment but to versions before that as well. Add automated cleanup logic to delete old backups and manage disk usage.
Environment Variable Management
Environment variables must never be committed to Git. The .env file should be created and managed directly on the server. Always add .env to your .gitignore.
# .gitignore — exclude environment files (required)
.env
.env.local
.env.production
.env.*.local# .env.example (this template DOES go into Git)
# Actual values are entered directly in the .env file on the server
DATABASE_URL=
AUTH_SECRET=
NEXT_PUBLIC_SITE_URL=If .env was accidentally committed, rotate all secrets immediately.Simply deleting the file from Git is not enough — the commit history retains the data. All API keys, database passwords, and other secrets must be changed right away.
Multi-Server Synchronization
When running 2+ servers, the deployment script must deploy to all servers sequentially. If deployment fails on one server, the remaining servers should not proceed — a safety mechanism to prevent partial deployments.
#!/bin/bash
set -e
SERVERS=("server1" "server2")
DEPLOY_SCRIPT="/home/deploy/my-app/deploy.sh"
for server in "${SERVERS[@]}"; do
echo "=== Deploying to $server ==="
ssh $server "bash $DEPLOY_SCRIPT"
if [ $? -ne 0 ]; then
echo "ERROR: $server deployment failed. Aborting."
exit 1
fi
echo "=== $server deployment complete ==="
done
echo "All servers deployed successfully"| Sync Method | Best For | Considerations |
|---|---|---|
| git pull + build per server | Small server count | Build time multiplied by server count |
| Build on one server, rsync to others | Apps with long build times | Build server environment must match |
| Docker image deployment | Large-scale services | Docker infrastructure setup cost |
Post-Deployment Monitoring
Deployment completion doesn't mean the job is done. Service health must be verified immediately after every deployment. Site accessibility checks, process manager status verification, and error log inspection are integral parts of the deployment process.
# 1. Check PM2 status (restart count, memory usage)
pm2 status
# 2. Check recent error logs
pm2 logs my-app --err --lines 20
# 3. Verify HTTP status code
curl -s -o /dev/null -w "%{http_code}" https://example.com
# 4. Measure response time
curl -s -o /dev/null -w "Response time: %{time_total}s\n" https://example.comSend deployment results as notifications. Adding a Slack webhook or Discord webhook call at the end of the deployment script lets the entire team track deployment status in real time. Include deployment success/failure status, duration, and deployer information.
Summary
Write a deployment script to eliminate manual steps. A single shell script handles git pull, dependency installation, build, and process restart — reducing deployment to one command.
Use npm ci for consistent dependency versions. Unlike npm install, npm ci installs exact versions from package-lock.json without modifying the lock file, guaranteeing environment parity.
Keep build backups for instant rollback. Maintain at least 3 previous builds. Swapping the .next directory and restarting takes 5 seconds versus 2-5 minutes for a git revert and rebuild.
Never commit .env files to Git. Manage environment variables directly on the server. Use .env.example as a committed template. If secrets are accidentally committed, rotate all credentials immediately.
Deploy to multiple servers sequentially with automatic abort on failure.If one server fails, stop the pipeline to prevent inconsistent deployments across the infrastructure.
Automate post-deployment monitoring and notifications. Verify HTTP status codes, check error logs, measure response times, and send results to team communication channels.
The deployment scripts in this article are provided as examples and require adaptation to your specific environment. Security-related configurations (SSH keys, environment variables) should follow your organization's security policies.