This comprehensive guide walks you through deploying the BMI & Health Tracker full-stack application (React + Node.js + PostgreSQL) on a fresh AWS Ubuntu EC2 server using Nginx as a reverse proxy and static file server.
- Health Metrics Calculation: BMI, BMR (using Mifflin-St Jeor equation), and daily calorie needs
- Custom Measurement Dates: Track measurements with specific dates (not just "now") - great for historical data
- 30-Day BMI Trend Visualization: Interactive charts showing your BMI progress over time
- Multiple Activity Levels: From sedentary to very active lifestyle calculations
- Real-time Stats Dashboard: Quick view of your current metrics and total measurements
- Responsive Design: Works seamlessly on desktop and mobile devices
- AWS Account with EC2 access
- Domain name (optional, but recommended for HTTPS)
- SSH client (Terminal, PuTTY, or similar)
- Basic knowledge of Linux commands
- Sign in to AWS Console → Navigate to EC2 Dashboard
- Click "Launch Instance"
- Configure Instance:
- Name:
bmi-health-tracker-server - AMI: Ubuntu Server 22.04 LTS (or latest LTS)
- Instance Type:
t2.micro(free tier) ort2.small(recommended) - Key Pair: Create new or use existing
.pemkey (download and save securely) - Network Settings:
- Create security group or use existing
- Allow SSH (port 22) from your IP
- Allow HTTP (port 80) from anywhere (0.0.0.0/0)
- Allow HTTPS (port 443) from anywhere (0.0.0.0/0) - if using SSL
- Storage: 20 GB gp3 (minimum 10 GB recommended)
- Name:
- Launch Instance and wait for it to be in "Running" state
Add these inbound rules to your security group:
| Type | Protocol | Port Range | Source | Description |
|---|---|---|---|---|
| SSH | TCP | 22 | My IP | SSH access |
| HTTP | TCP | 80 | 0.0.0.0/0 | HTTP web traffic |
| HTTPS | TCP | 443 | 0.0.0.0/0 | HTTPS secure traffic |
# Set permissions for your .pem file (first time only)
chmod 400 your-key.pem
# Connect via SSH
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IPReplace YOUR_EC2_PUBLIC_IP with your instance's public IP from AWS Console.
# Update package lists and upgrade existing packages
sudo apt update && sudo apt upgrade -y
# Install essential build tools and utilities
sudo apt install -y git curl wget build-essential nginx ufw ca-certificates gnupg# Download and install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Load NVM into current session
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Install Node.js LTS version
nvm install --lts
# Verify installation
node -v # Should show v20.x.x or similar
npm -v # Should show 10.x.x or similarNote: If you disconnect and reconnect, NVM will auto-load via .bashrc.
# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib
# Start and enable PostgreSQL service
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Verify it's running
sudo systemctl status postgresql# Switch to postgres user and create database user
sudo -u postgres createuser --interactive --pwprompt
# Enter details:
# Username: bmi_user
# Password: [ostad2025]
# Superuser: n
# Create databases: n
# Create roles: n
# Create database owned by bmi_user
sudo -u postgres createdb -O bmi_user bmidb
# Test connection
psql -U bmi_user -d bmidb -h localhost
# Enter password when prompted
# Type \q to quit# Edit pg_hba.conf to ensure local connections work
sudo nano /etc/postgresql/*/main/pg_hba.conf
# Ensure this line exists (should be there by default):
# local all all md5
# host all all 127.0.0.1/32 md5
# Restart PostgreSQL
sudo systemctl restart postgresqlOption A: Clone from GitHub (recommended)
cd /home/ubuntu
git clone https://github.com/md-sarowar-alam/single-server-3tier-webapp-github-actions.git
cd single-server-3tier-webapp-github-actionsOption B: Upload via SCP
# On your local machine:
scp -i your-key.pem -r ./bmi-health-tracker ubuntu@YOUR_EC2_PUBLIC_IP:/home/ubuntu/
# Then SSH and navigate:
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP
cd /home/ubuntu/single-server-3tier-webappOption C: Upload as ZIP
# On local machine:
zip -r bmi-health-tracker.zip bmi-health-tracker
scp -i your-key.pem bmi-health-tracker.zip ubuntu@YOUR_EC2_PUBLIC_IP:/home/ubuntu/
# On server:
sudo apt install -y unzip
unzip bmi-health-tracker.zip
cd bmi-health-trackercd /home/ubuntu/single-server-3tier-webapp/backend
# Create environment file from example
cp .env.example .env
# Edit environment variables
nano .envConfigure .env file:
PORT=3000
DATABASE_URL=postgresql://bmi_user:YOUR_DB_PASSWORD@localhost:5432/bmidb
NODE_ENV=productionReplace YOUR_DB_PASSWORD with the password you created for bmi_user.
Install backend dependencies:
npm install --productionRun database migrations:
# Apply migrations in order to create and update the measurements table
# Migration 001: Create measurements table
psql -U bmi_user -d bmidb -h localhost -f migrations/001_create_measurements.sql
# Enter password when prompted
# Migration 002: Add measurement_date column for custom date tracking
psql -U bmi_user -d bmidb -h localhost -f migrations/002_add_measurement_date.sql
# Enter password when prompted
# Verify migrations were successful
psql -U bmi_user -d bmidb -h localhost -c "\d measurements"
# You should see the measurements table structure with measurement_date columnWhat these migrations do:
- Migration 001: Creates the
measurementstable with all health metrics (BMI, BMR, calories, etc.) - Migration 002: Adds the
measurement_datecolumn to allow users to specify when a measurement was taken (not just when it was recorded)
Test backend locally (optional):
npm start
# Should see: "Server started"
# Press Ctrl+C to stopcd /home/ubuntu/single-server-3tier-webapp/frontend
# Install dependencies
npm install
# Build for production
npm run buildThis creates a dist/ folder with optimized static files.
Deploy built files to Nginx web directory:
# Create directory for frontend
sudo mkdir -p /var/www/bmi-health-tracker
# Copy built files
sudo cp -r dist/* /var/www/bmi-health-tracker/
# Set proper ownership
sudo chown -R www-data:www-data /var/www/bmi-health-tracker
# Verify files copied
ls -la /var/www/bmi-health-trackerPM2 keeps your Node.js backend running continuously and restarts it on crashes or server reboots.
npm install -g pm2cd /home/ubuntu/single-server-3tier-webapp/backend
# Start the backend server
pm2 start src/server.js --name bmi-backend
# Check status
pm2 status
# View logs
pm2 logs bmi-backend
# Save PM2 process list
pm2 save# Generate startup script
pm2 startup systemd -u ubuntu --hp /home/ubuntu
# Run the command that PM2 outputs (it will be something like):
# sudo env PATH=$PATH:/home/ubuntu/.nvm/versions/node/vXX.X.X/bin ...
# Copy and paste that exact command
# Verify auto-start is configured
sudo systemctl status pm2-ubuntupm2 list # List all processes
pm2 restart bmi-backend # Restart backend
pm2 stop bmi-backend # Stop backend
pm2 delete bmi-backend # Remove from PM2
pm2 logs bmi-backend # View live logs
pm2 logs bmi-backend --lines 100 # Last 100 lines
pm2 monit # Monitor CPU/memorysudo nano /etc/nginx/sites-available/bmi-health-trackerPaste this configuration:
server {
listen 80;
listen [::]:80;
# Replace with your domain or EC2 public IP
server_name YOUR_DOMAIN_OR_IP;
# Frontend static files
root /var/www/bmi-health-tracker;
index index.html;
# Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/javascript application/json;
# Frontend routing (React Router)
location / {
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Backend API proxy
location /api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_http_version 1.1;
# WebSocket support (if needed in future)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
# Standard proxy headers
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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Disable caching for API
proxy_cache_bypass $http_upgrade;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Hide nginx version
server_tokens off;
# Logs
access_log /var/log/nginx/bmi-access.log;
error_log /var/log/nginx/bmi-error.log;
}Replace YOUR_DOMAIN_OR_IP with:
- Your domain name (e.g.,
bmi.example.com) if you have one - Your EC2 public IP (e.g.,
3.15.123.45) if you don't have a domain
# Create symbolic link to enable site
sudo ln -s /etc/nginx/sites-available/bmi-health-tracker /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# If test passes, reload Nginx
sudo systemctl reload nginx
# Check Nginx status
sudo systemctl status nginx# Start Nginx if not running
sudo systemctl start nginx
# Enable auto-start on boot
sudo systemctl enable nginxSecure your server by enabling Ubuntu's firewall.
# Check UFW status
sudo ufw status
# Allow SSH (CRITICAL - do this first!)
sudo ufw allow OpenSSH
# Or if using custom SSH port:
# sudo ufw allow 2222/tcp
# Allow HTTP traffic
sudo ufw allow 'Nginx HTTP'
# Allow HTTPS traffic (for future SSL)
sudo ufw allow 'Nginx HTTPS'
# Or allow specific ports:
# sudo ufw allow 80/tcp
# sudo ufw allow 443/tcp
# Enable firewall
sudo ufw enable
# Verify rules
sudo ufw status verboseExpected output:
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx HTTP ALLOW Anywhere
Nginx HTTPS ALLOW Anywhere
Use Let's Encrypt to secure your site with free SSL certificates.
- A domain name pointed to your EC2 instance's public IP
- DNS A record configured (wait 5-10 minutes for propagation)
# Install Certbot and Nginx plugin
sudo apt install -y certbot python3-certbot-nginx# Replace YOUR_DOMAIN with your actual domain
sudo certbot --nginx -d YOUR_DOMAIN
# Or for both www and non-www:
# sudo certbot --nginx -d example.com -d www.example.comFollow the prompts:
- Enter email address
- Agree to terms
- Choose whether to share email with EFF
- Choose whether to redirect HTTP to HTTPS (recommended: Yes)
# Test renewal process (dry run)
sudo certbot renew --dry-run
# Certificates auto-renew via systemd timer
sudo systemctl status certbot.timerVisit https://YOUR_DOMAIN in your browser. You should see a padlock icon.
# Test health endpoint
curl http://localhost:3000/health
# Expected: {"status":"ok","environment":"production"}
# Test get all measurements
curl http://localhost:3000/api/measurements
# Expected: {"rows":[]} (empty array initially)
# Test creating a measurement with custom date
curl -X POST http://localhost:3000/api/measurements \
-H "Content-Type: application/json" \
-d '{
"weightKg": 70,
"heightCm": 175,
"age": 30,
"sex": "male",
"activity": "moderate",
"measurementDate": "2025-12-15"
}'
# Expected response includes calculated values:
# {"measurement":{"id":1,"bmi":"22.9","bmi_category":"Normal","bmr":1732,"daily_calories":2685,...}}
# Test from public internet (replace with your IP/domain)
curl http://YOUR_EC2_PUBLIC_IP/api/measurements
# Test 30-day trends endpoint
curl http://localhost:3000/api/measurements/trends
# Returns BMI averages grouped by date for the last 30 daysOpen browser and navigate to:
http://YOUR_EC2_PUBLIC_IPorhttp://YOUR_DOMAINorhttps://YOUR_DOMAIN(if SSL configured)
Check these features:
- ✅ Form displays with all 6 input fields:
- Measurement Date (new feature! allows entering historical data)
- Weight (kg)
- Height (cm)
- Age (years)
- Biological Sex
- Activity Level
- ✅ Submit a measurement (defaults to today's date, but you can change it)
- ✅ Measurement appears in the recent list with the specified date
- ✅ Stats cards display current BMI, BMR, and daily calorie needs
- ✅ Trend chart displays (shows 30-day BMI trends)
- ✅ Check browser console for errors (F12)
Test the measurement date feature:
- Try entering a measurement from a past date (e.g., last week)
- Verify it appears with the correct date in the measurements list
- The date field has a max limit (cannot select future dates)
# Connect to database
psql -U bmi_user -d bmidb -h localhost
# View all measurements with dates
SELECT id, weight_kg, height_cm, bmi, bmi_category, measurement_date, created_at
FROM measurements
ORDER BY measurement_date DESC;
# View 30-day BMI trends (same query the chart uses)
SELECT measurement_date AS day, AVG(bmi) AS avg_bmi
FROM measurements
WHERE measurement_date >= CURRENT_DATE - interval '30 days'
GROUP BY measurement_date
ORDER BY measurement_date;
# Count total measurements
SELECT COUNT(*) FROM measurements;
# View table structure (verify measurement_date column exists)
\d measurements
# Exit
\qNote: The measurement_date column allows users to track when measurements were actually taken, which is different from created_at (when the record was entered into the system).
Backend logs:
pm2 logs bmi-backend
pm2 logs bmi-backend --lines 50Nginx access logs:
sudo tail -f /var/log/nginx/bmi-access.logNginx error logs:
sudo tail -f /var/log/nginx/bmi-error.logPostgreSQL logs:
sudo tail -f /var/log/postgresql/postgresql-*-main.log# Navigate to project directory
cd /home/ubuntu/single-server-3tier-webapp
# Pull latest changes (if using Git)
git pull origin main
# Update backend
cd backend
npm install --production
pm2 restart bmi-backend
# Update frontend
cd ../frontend
npm install
npm run build
sudo rm -rf /var/www/bmi-health-tracker/*
sudo cp -r dist/* /var/www/bmi-health-tracker/
sudo chown -R www-data:www-data /var/www/bmi-health-trackerIssue: Backend not accessible
# Check PM2 status
pm2 status
# Restart backend
pm2 restart bmi-backend
# Check logs
pm2 logs bmi-backend --lines 100Issue: Database connection failed
# Verify PostgreSQL is running
sudo systemctl status postgresql
# Test connection
psql -U bmi_user -d bmidb -h localhost
# Check backend .env file has correct DATABASE_URL
cat /home/ubuntu/single-server-3tier-webapp/backend/.envIssue: Nginx 502 Bad Gateway
# Verify backend is running
pm2 status
curl http://localhost:3000/api/measurements
# Check Nginx error logs
sudo tail -100 /var/log/nginx/bmi-error.log
# Test Nginx config
sudo nginx -t
sudo systemctl restart nginxIssue: Cannot connect via HTTP
# Check UFW firewall
sudo ufw status
# Ensure ports 80/443 are open
sudo ufw allow 'Nginx Full'
# Check Nginx is listening
sudo netstat -tlnp | grep nginx
# Check AWS Security Group has port 80/443 openIssue: Frontend shows blank page
# Check if files were copied
ls -la /var/www/bmi-health-tracker/
# Check browser console for errors (F12)
# Verify Nginx is serving files
curl http://YOUR_EC2_PUBLIC_IP
# Check Nginx error logs
sudo tail -50 /var/log/nginx/bmi-error.logProblem: Measurement dates not showing correctly
# Check if migration 002 was applied
psql -U bmi_user -d bmidb -h localhost -c "\d measurements" | grep measurement_date
# If column is missing, run migration 002:
cd /home/ubuntu/single-server-3tier-webapp/backend
psql -U bmi_user -d bmidb -h localhost -f migrations/002_add_measurement_date.sql
# Restart backend
pm2 restart bmi-backendProblem: Cannot select dates in the form
# Verify frontend is properly deployed
ls -la /var/www/bmi-health-tracker/
# Check for JavaScript errors in browser console (F12)
# Rebuild and redeploy if needed:
cd /home/ubuntu/single-server-3tier-webapp/frontend
npm run build
sudo cp -r dist/* /var/www/bmi-health-tracker/
sudo chown -R www-data:www-data /var/www/bmi-health-trackerProblem: Backend not accepting measurementDate field
# Check backend code is current
cd /home/ubuntu/single-server-3tier-webapp/backend
grep -r "measurementDate" src/routes.js
# If not found, ensure you have the latest code
# Then restart:
pm2 restart bmi-backend# Create backup directory
mkdir -p /home/ubuntu/backups
# Backup database
pg_dump -U bmi_user -h localhost bmidb > /home/ubuntu/backups/bmidb_$(date +%Y%m%d_%H%M%S).sql
# Restore database (if needed)
psql -U bmi_user -h localhost bmidb < /home/ubuntu/backups/bmidb_20251212_120000.sqlCreate a backup script:
nano /home/ubuntu/backup-db.shAdd:
#!/bin/bash
BACKUP_DIR="/home/ubuntu/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
pg_dump -U bmi_user -h localhost bmidb > $BACKUP_DIR/bmidb_$TIMESTAMP.sql
# Keep only last 7 days
find $BACKUP_DIR -name "bmidb_*.sql" -mtime +7 -deleteMake executable and add to cron:
chmod +x /home/ubuntu/backup-db.sh
# Add to crontab (daily at 2 AM)
crontab -e
# Add this line:
0 2 * * * /home/ubuntu/backup-db.sh# Check disk space
df -h
# Check memory usage
free -h
# Check CPU usage
top
# Press 'q' to quit
# Check PM2 resource usage
pm2 monit
# Check system resources
htop # (install with: sudo apt install htop)After successful deployment, your server structure looks like:
/home/ubuntu/
├── bmi-health-tracker/
│ ├── backend/
│ │ ├── src/
│ │ │ ├── server.js
│ │ │ ├── routes.js
│ │ │ ├── db.js
│ │ │ └── calculations.js
│ │ ├── migrations/
│ │ │ └── 001_create_measurements.sql
│ │ ├── .env # Environment variables
│ │ ├── package.json
│ │ └── node_modules/
│ └── frontend/
│ ├── dist/ # Build output (copied to /var/www)
│ ├── src/
│ ├── package.json
│ └── node_modules/
│
/var/www/bmi-health-tracker/ # Production frontend files
├── index.html
├── assets/
│ ├── index-[hash].js
│ └── index-[hash].css
└── ...
/etc/nginx/
└── sites-available/
└── bmi-health-tracker # Nginx configuration
/var/log/nginx/
├── bmi-access.log # Access logs
└── bmi-error.log # Error logs
# Update regularly
sudo apt update && sudo apt upgrade -y
# Enable automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades# Edit SSH config
sudo nano /etc/ssh/sshd_config
# Recommended changes:
# PermitRootLogin no
# PasswordAuthentication no
# Port 2222 # Change default port (optional)
# Restart SSH
sudo systemctl restart sshdEnsure your PostgreSQL user has a strong password (at least 16 characters, mixed case, numbers, symbols).
Never commit .env file to Git. Keep it secure with proper permissions:
chmod 600 /home/ubuntu/single-server-3tier-webapp/backend/.env- Backup database daily (see Part 10.4)
- Backup application code to Git
- Consider AWS snapshots for entire EC2 instance
- Development/Testing: t2.micro (1 vCPU, 1 GB RAM) - Free tier eligible
- Low Traffic: t2.small (1 vCPU, 2 GB RAM) - ~$17/month
- Medium Traffic: t2.medium (2 vCPU, 4 GB RAM) - ~$34/month
For production, consider 1-year Reserved Instances for ~40% savings.
# From AWS Console or CLI
aws ec2 stop-instances --instance-ids i-1234567890abcdef0Note: You still pay for EBS storage when stopped.
After your application is successfully running, you'll need to update it when new features are added or bugs are fixed. This section covers how to safely update your application with minimal downtime.
Before performing any update:
# 1. Create database backup
sudo -u postgres pg_dump -Fc bmidb > /var/backups/postgresql/bmidb_$(date +%Y%m%d_%H%M%S).dump
# 2. Check current application status
pm2 status
sudo systemctl status nginx
sudo systemctl status postgresql
# 3. Note current version/commit
cd /home/ubuntu/single-server-3tier-webapp
git log -1 --oneline# Navigate to project directory
cd /home/ubuntu/single-server-3tier-webapp/backend
# Fetch latest changes
git fetch origin main
# Check what will change
git log HEAD..origin/main --oneline
# Pull latest code
git pull origin main
# Install new dependencies (if package.json changed)
npm install
# Run new database migrations (if any)
# Check migrations folder first
ls -la migrations/
# If new migrations exist, run them
node -e "
const pool = require('./src/db');
const fs = require('fs');
const path = require('path');
async function runMigrations() {
const client = await pool.connect();
try {
const migrations = fs.readdirSync('./migrations').sort();
for (const file of migrations) {
console.log(\`Running migration: \${file}\`);
const sql = fs.readFileSync(path.join('./migrations', file), 'utf8');
await client.query(sql);
console.log(\`✓ Completed: \${file}\`);
}
} finally {
client.release();
}
}
runMigrations();
"
# Restart backend with PM2 (zero-downtime reload)
pm2 reload bmi-backend
# Verify backend is running
pm2 status
pm2 logs bmi-backend --lines 50
# Test API endpoint
curl http://localhost:3000/health# If you uploaded files manually (via SCP/SFTP)
# Navigate to backend directory
cd /home/ubuntu/single-server-3tier-webapp/backend
# Backup current code
cp -r /home/ubuntu/single-server-3tier-webapp/backend /home/ubuntu/single-server-3tier-webapp/backend.backup.$(date +%Y%m%d_%H%M%S)
# Upload new files (from your local machine)
# scp -i your-key.pem -r ./backend/* ubuntu@YOUR_EC2_IP:/home/ubuntu/single-server-3tier-webapp/backend/
# On the server, install dependencies
npm install
# Run migrations (if needed)
# Check for new migration files in migrations/ folder
# Restart backend
pm2 reload bmi-backend
# Monitor logs
pm2 logs bmi-backend# Navigate to frontend directory
cd /home/ubuntu/single-server-3tier-webapp/frontend
# Pull latest code (if using Git)
git pull origin main
# Install new dependencies
npm install
# Build production bundle
npm run build
# The build process creates optimized files in dist/ folder
# Backup current frontend
sudo cp -r /var/www/bmi-tracker /var/www/bmi-tracker.backup.$(date +%Y%m%d_%H%M%S)
# Copy new build to Nginx directory
sudo rm -rf /var/www/bmi-tracker/*
sudo cp -r dist/* /var/www/bmi-tracker/
# Set proper permissions
sudo chown -R www-data:www-data /var/www/bmi-tracker
sudo chmod -R 755 /var/www/bmi-tracker
# Clear browser cache by updating Nginx headers (optional)
sudo nano /etc/nginx/sites-available/bmi-health-tracker
# Add cache busting for new deployments:
# location / {
# try_files $uri $uri/ /index.html;
# add_header Cache-Control "no-cache, must-revalidate";
# }
# Reload Nginx (no downtime)
sudo nginx -t
sudo systemctl reload nginx
# Verify frontend
curl http://localhost/# Navigate to backend directory
cd /home/ubuntu/single-server-3tier-webapp/backend
# List existing migrations
ls -la migrations/
# If new migration files exist (e.g., 003_add_new_feature.sql)
# Run them manually:
sudo -u postgres psql -d bmidb -f migrations/003_add_new_feature.sql
# Or create a migration runner script
cat > run_migrations.sh << 'EOF'
#!/bin/bash
for migration in migrations/*.sql; do
echo "Running $migration..."
sudo -u postgres psql -d bmidb -f "$migration"
if [ $? -eq 0 ]; then
echo "✓ $migration completed"
else
echo "✗ $migration failed"
exit 1
fi
done
EOF
chmod +x run_migrations.sh
./run_migrations.sh
# Verify schema changes
sudo -u postgres psql -d bmidb
-- Check tables
\dt
-- Check specific table structure
\d measurements
-- Exit
\qFor production environments where downtime is unacceptable:
# 1. Create a deployment script
cat > /home/ubuntu/deploy.sh << 'EOF'
#!/bin/bash
set -e # Exit on error
echo "========================================="
echo "BMI Tracker Deployment Script"
echo "Started: $(date)"
echo "========================================="
# Configuration
PROJECT_DIR="/home/ubuntu/single-server-3tier-webapp"
BACKUP_DIR="/home/ubuntu/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Create backup directory
mkdir -p $BACKUP_DIR
# Step 1: Database backup
echo "[1/8] Creating database backup..."
sudo -u postgres pg_dump -Fc bmidb > $BACKUP_DIR/bmidb_$TIMESTAMP.dump
echo "✓ Database backup created"
# Step 2: Pull latest code
echo "[2/8] Pulling latest code..."
cd $PROJECT_DIR
git fetch origin main
git pull origin main
echo "✓ Code updated"
# Step 3: Update backend dependencies
echo "[3/8] Installing backend dependencies..."
cd $PROJECT_DIR/backend
npm install --production
echo "✓ Backend dependencies installed"
# Step 4: Run database migrations
echo "[4/8] Running database migrations..."
for migration in migrations/*.sql; do
if [ -f "$migration" ]; then
echo " Running $(basename $migration)..."
sudo -u postgres psql -d bmidb -f "$migration" 2>&1 | grep -v "already exists" || true
fi
done
echo "✓ Migrations completed"
# Step 5: Reload backend (zero-downtime)
echo "[5/8] Reloading backend..."
pm2 reload bmi-backend --update-env
sleep 3
echo "✓ Backend reloaded"
# Step 6: Update frontend dependencies
echo "[6/8] Installing frontend dependencies..."
cd $PROJECT_DIR/frontend
npm install
echo "✓ Frontend dependencies installed"
# Step 7: Build frontend
echo "[7/8] Building frontend..."
npm run build
echo "✓ Frontend built"
# Step 8: Deploy frontend
echo "[8/8] Deploying frontend..."
sudo cp -r dist/* /var/www/bmi-tracker/
sudo chown -R www-data:www-data /var/www/bmi-tracker
sudo chmod -R 755 /var/www/bmi-tracker
echo "✓ Frontend deployed"
# Reload Nginx
echo "Reloading Nginx..."
sudo nginx -t && sudo systemctl reload nginx
echo "✓ Nginx reloaded"
# Verify deployment
echo ""
echo "========================================="
echo "Verification"
echo "========================================="
# Check backend
if pm2 status | grep -q "online"; then
echo "✓ Backend: Online"
else
echo "✗ Backend: Error"
fi
# Check API
if curl -s http://localhost:3000/health | grep -q "ok"; then
echo "✓ API Health Check: Passed"
else
echo "✗ API Health Check: Failed"
fi
# Check frontend
if [ -f "/var/www/bmi-tracker/index.html" ]; then
echo "✓ Frontend Files: Present"
else
echo "✗ Frontend Files: Missing"
fi
echo ""
echo "========================================="
echo "Deployment completed: $(date)"
echo "Backup location: $BACKUP_DIR"
echo "========================================="
EOF
# Make script executable
chmod +x /home/ubuntu/deploy.sh
# Run deployment
/home/ubuntu/deploy.shIf an update causes issues:
# 1. Identify the issue
pm2 logs bmi-backend --lines 100
sudo tail -100 /var/log/nginx/error.log
# 2. Rollback Backend Code
cd /home/ubuntu/single-server-3tier-webapp/backend
# Using Git
git log --oneline -5
git checkout <previous-commit-hash>
npm install
pm2 reload bmi-backend
# Or restore from backup
# cp -r /home/ubuntu/single-server-3tier-webapp/backend.backup.TIMESTAMP/* /home/ubuntu/single-server-3tier-webapp/backend/
# 3. Rollback Database (if schema changed)
# Restore from backup
sudo -u postgres psql -c "DROP DATABASE bmidb;"
sudo -u postgres psql -c "CREATE DATABASE bmidb OWNER bmi_user;"
sudo -u postgres pg_restore -d bmidb /var/backups/postgresql/bmidb_TIMESTAMP.dump
# 4. Rollback Frontend
sudo cp -r /var/www/bmi-tracker.backup.TIMESTAMP/* /var/www/bmi-tracker/
sudo systemctl reload nginx
# 5. Verify rollback
curl http://localhost:3000/health
curl http://localhost/
pm2 logs bmi-backend# Create health check script
cat > /home/ubuntu/health_check.sh << 'EOF'
#!/bin/bash
echo "========================================="
echo "Application Health Check - $(date)"
echo "========================================="
# Check PM2 status
echo ""
echo "1. Backend Process (PM2):"
if pm2 status | grep -q "bmi-backend.*online"; then
echo " ✓ Backend is running"
else
echo " ✗ Backend is not running"
fi
# Check API health
echo ""
echo "2. API Health Endpoint:"
HEALTH_RESPONSE=$(curl -s http://localhost:3000/health)
if echo $HEALTH_RESPONSE | grep -q "ok"; then
echo " ✓ API responding: $HEALTH_RESPONSE"
else
echo " ✗ API not responding properly"
fi
# Check database connection
echo ""
echo "3. Database Connection:"
if sudo -u postgres psql -d bmidb -c "SELECT 1;" > /dev/null 2>&1; then
echo " ✓ Database accessible"
else
echo " ✗ Database connection failed"
fi
# Check Nginx
echo ""
echo "4. Nginx Status:"
if sudo systemctl is-active --quiet nginx; then
echo " ✓ Nginx is running"
else
echo " ✗ Nginx is not running"
fi
# Check frontend files
echo ""
echo "5. Frontend Deployment:"
if [ -f "/var/www/bmi-tracker/index.html" ]; then
echo " ✓ Frontend files present"
else
echo " ✗ Frontend files missing"
fi
# Check disk space
echo ""
echo "6. Disk Space:"
df -h / | awk 'NR==2 {print " Used: "$3" / Available: "$4" ("$5" full)"}'
echo ""
echo "========================================="
EOF
chmod +x /home/ubuntu/health_check.sh
# Run health check
/home/ubuntu/health_check.shFor automatic deployments when you push to GitHub:
# Install webhook listener
npm install -g webhook
# Create webhook configuration
cat > /home/ubuntu/webhook-config.json << 'EOF'
[
{
"id": "deploy-bmi-tracker",
"execute-command": "/home/ubuntu/deploy.sh",
"command-working-directory": "/home/ubuntu/single-server-3tier-webapp",
"pass-arguments-to-command": [],
"trigger-rule": {
"match": {
"type": "payload-hash-sha1",
"secret": "YOUR_WEBHOOK_SECRET",
"parameter": {
"source": "header",
"name": "X-Hub-Signature"
}
}
}
}
]
EOF
# Create webhook service
sudo nano /etc/systemd/system/webhook.service
# Add:
[Unit]
Description=Webhook Service
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
ExecStart=/usr/local/bin/webhook -hooks /home/ubuntu/webhook-config.json -port 9000
Restart=always
[Install]
WantedBy=multi-user.target
# Enable and start webhook service
sudo systemctl daemon-reload
sudo systemctl enable webhook
sudo systemctl start webhook
# Add webhook port to firewall
sudo ufw allow 9000/tcp
# Configure in GitHub:
# Repository → Settings → Webhooks → Add webhook
# Payload URL: http://YOUR_EC2_IP:9000/hooks/deploy-bmi-tracker
# Content type: application/json
# Secret: YOUR_WEBHOOK_SECRET
# Events: Just the push event# Add to crontab for automated maintenance
crontab -e
# Daily database backup at 2 AM
0 2 * * * /usr/local/bin/backup_bmi_db.sh >> /var/log/postgresql/backup.log 2>&1
# Weekly log rotation (Sunday 3 AM)
0 3 * * 0 pm2 flush
# Weekly health check report (Monday 9 AM)
0 9 * * 1 /home/ubuntu/health_check.sh | mail -s "BMI Tracker Health Report" [email protected]
# Monthly server updates (1st day, 4 AM)
0 4 1 * * sudo apt update && sudo apt upgrade -y && sudo systemctl rebootDO:
- ✓ Always create backups before updates
- ✓ Test updates in development first
- ✓ Use
pm2 reloadinstead ofpm2 restart(zero-downtime) - ✓ Monitor logs after deployment
- ✓ Keep dependencies updated regularly
- ✓ Document changes and version numbers
- ✓ Use Git tags for production releases
DON'T:
- ✗ Update directly on production without testing
- ✗ Skip database backups
- ✗ Use
pm2 deleteandpm2 start(causes downtime) - ✗ Forget to run migrations
- ✗ Leave old backup files accumulating
- ✗ Update during peak traffic hours
# Quick backend update
cd /home/ubuntu/single-server-3tier-webapp/backend && git pull && npm install && pm2 reload bmi-backend
# Quick frontend update
cd /home/ubuntu/single-server-3tier-webapp/frontend && git pull && npm install && npm run build && sudo cp -r dist/* /var/www/bmi-tracker/
# Check application version
cd /home/ubuntu/single-server-3tier-webapp && git log -1 --oneline
# View recent changes
cd /home/ubuntu/single-server-3tier-webapp && git log -5 --oneline
# Restart everything safely
pm2 reload bmi-backend && sudo systemctl reload nginx
# Full restart (if needed)
pm2 restart bmi-backend && sudo systemctl restart nginx && sudo systemctl restart postgresqlYour BMI Health Tracker is now live and accessible!
# SSH to server
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP
# Check backend status
pm2 status
# View backend logs
pm2 logs bmi-backend
# Restart backend
pm2 restart bmi-backend
# Check Nginx status
sudo systemctl status nginx
# Reload Nginx config
sudo nginx -t && sudo systemctl reload nginx
# View database
psql -U bmi_user -d bmidb -h localhostCommon Resources:
- AWS EC2 Documentation: https://docs.aws.amazon.com/ec2/
- Nginx Documentation: https://nginx.org/en/docs/
- PM2 Documentation: https://pm2.keymetrics.io/docs/
- PostgreSQL Documentation: https://www.postgresql.org/docs/
Checklist:
- EC2 instance running
- Security group configured (ports 22, 80, 443)
- Node.js and PostgreSQL installed
- Database created and migrated
- Backend running via PM2
- Frontend built and deployed
- Nginx configured and running
- Firewall enabled
- Application accessible via browser
- SSL certificate installed (optional)
- Backups configured
Last Updated: December 16, 2025
Version: 2.1
Changes: Updated to include measurement_date feature (Migration 002), enhanced testing procedures, and current project state
🧑💻 Author
Md. Sarowar Alam
Lead DevOps Engineer, Hogarth Worldwide
📧 Email: [email protected]
🔗 LinkedIn: linkedin.com/in/sarowar