MinusNow Documentation
☁️ Google Cloud Platform 🐧 Ubuntu 22.04 / 24.04 🔒 Production-Grade 📦 v26.x 🚀 GitHub Actions CI/CD

GCP Deployment Guide

Complete step-by-step guide to deploy the MinusNow ITSM platform on Google Cloud Platform from your GitHub repository.
Covers project setup, Compute Engine VMs, Git clone, GitHub Actions CI/CD auto-deploy, Cloud SQL, networking, HTTPS, domain mapping, and production hardening.

📑 Table of Contents

  1. Prerequisites & GCP Account Setup
  2. Create a GCP Project & Enable APIs
  3. VPC Network & Firewall Rules
  4. Create Compute Engine VM
  5. Install Node.js & Dependencies
  6. Deploy MinusNow Application (Git Clone from GitHub)
  7. Database Setup (SQLite or Cloud SQL)
  8. Process Manager (PM2) & Auto-Start
  9. GitHub Actions CI/CD — Auto-Deploy on Push
  10. Nginx Reverse Proxy Setup
  11. Domain & Cloud DNS Configuration
  12. SSL/TLS Certificate (Let's Encrypt)
  13. Cloud Load Balancer (Optional — Production)
  14. Monitoring & Logging (Cloud Operations)
  15. Backup & Disaster Recovery
  16. Security Hardening
  17. Go-Live Checklist
  18. Cost Estimation
  19. Troubleshooting

1) Prerequisites & GCP Account Setup

What You Need

  • A Google account (Gmail or Workspace)
  • A GCP account with billing enabled — Free trial: $300 credit for 90 days
  • A domain name (e.g. minusnow.yourdomain.com)
  • Your MinusNow code pushed to a GitHub repository (private or public)
  • Basic SSH / Linux terminal skills

Recommended VM Sizing

TierMachine TypeRAMDiskNodes
Dev/POCe2-medium4 GB30 GB≤ 50
Small Prode2-standard-28 GB60 GB≤ 200
Mediume2-standard-416 GB120 GB≤ 1,000
Largee2-standard-832 GB250 GB≤ 5,000

Install Google Cloud CLI (gcloud)

Install the gcloud CLI on your local machine to manage GCP resources from the terminal.

Windows (PowerShell)
(New-Object Net.WebClient).DownloadFile("https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe", "$env:TEMP\GoogleCloudSDKInstaller.exe") Start-Process "$env:TEMP\GoogleCloudSDKInstaller.exe" -Wait

Linux / macOS
curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz tar -xf google-cloud-cli-linux-x86_64.tar.gz ./google-cloud-sdk/install.sh source ~/.bashrc

Authenticate
# Login to your Google account gcloud auth login # Verify authentication gcloud auth list

2) Create a GCP Project & Enable APIs

Create a New Project

Every resource in GCP lives inside a Project. Create one dedicated to MinusNow.

1

Create the project

# Create a new project (replace YOUR_PROJECT_ID with a unique ID) gcloud projects create minusnow-prod --name="MinusNow Production" # Set it as the active project gcloud config set project minusnow-prod

Or via Console: console.cloud.google.com → hamburger menu → IAM & AdminCreate Project

2

Link a billing account

# List available billing accounts gcloud billing accounts list # Link billing to your project gcloud billing projects link minusnow-prod --billing-account=YOUR_BILLING_ACCOUNT_ID

Or via Console: BillingLink a billing account

3

Enable required APIs

gcloud services enable compute.googleapis.com \ sqladmin.googleapis.com \ dns.googleapis.com \ logging.googleapis.com \ monitoring.googleapis.com \ iap.googleapis.com \ secretmanager.googleapis.com
Compute Engine Cloud SQL Admin Cloud DNS Cloud Logging Cloud Monitoring Identity-Aware Proxy Secret Manager

3) VPC Network & Firewall Rules

Create a Custom VPC

Use a custom VPC for better security and isolation.

# Create VPC network gcloud compute networks create minusnow-vpc \ --subnet-mode=custom # Create subnet in your preferred region gcloud compute networks subnets create minusnow-subnet \ --network=minusnow-vpc \ --region=asia-south1 \ --range=10.10.0.0/24

Choose a region close to your users. asia-south1 = Mumbai, India. us-central1 = Iowa, US. europe-west1 = Belgium, EU.

Firewall Rules

Allow only the traffic MinusNow needs.

# Allow SSH (port 22) gcloud compute firewall-rules create allow-ssh \ --network=minusnow-vpc \ --allow=tcp:22 \ --source-ranges=YOUR_IP/32 \ --target-tags=minusnow-server # Allow HTTP + HTTPS (ports 80, 443) gcloud compute firewall-rules create allow-http-https \ --network=minusnow-vpc \ --allow=tcp:80,tcp:443 \ --source-ranges=0.0.0.0/0 \ --target-tags=minusnow-server # Allow internal app port (5000) gcloud compute firewall-rules create allow-internal-app \ --network=minusnow-vpc \ --allow=tcp:5000 \ --source-ranges=10.10.0.0/24 \ --target-tags=minusnow-server
⚠️ Security: Replace YOUR_IP/32 with your actual public IP for SSH. Never use 0.0.0.0/0 for SSH in production.

Firewall Rules Reference

Rule NameDirectionPortsSourcePurpose
allow-sshIngressTCP 22Your IP onlySSH administration
allow-http-httpsIngressTCP 80, 4430.0.0.0/0Web traffic
allow-internal-appIngressTCP 500010.10.0.0/24 (VPC)App server (internal)
allow-health-checkIngressTCP 5000130.211.0.0/22, 35.191.0.0/16GCP health checks (if using LB)

4) Create Compute Engine VM

Launch the VM

1

Create the instance via gcloud CLI

gcloud compute instances create minusnow-app \ --zone=asia-south1-a \ --machine-type=e2-standard-2 \ --image-family=ubuntu-2204-lts \ --image-project=ubuntu-os-cloud \ --boot-disk-size=60GB \ --boot-disk-type=pd-ssd \ --network=minusnow-vpc \ --subnet=minusnow-subnet \ --tags=minusnow-server \ --metadata=startup-script='#!/bin/bash apt-get update && apt-get upgrade -y'
2

Reserve a static external IP

# Reserve a static IP gcloud compute addresses create minusnow-ip \ --region=asia-south1 # Get the reserved IP address gcloud compute addresses describe minusnow-ip \ --region=asia-south1 --format="value(address)" # Assign it to the VM gcloud compute instances delete-access-config minusnow-app \ --zone=asia-south1-a --access-config-name="External NAT" gcloud compute instances add-access-config minusnow-app \ --zone=asia-south1-a \ --access-config-name="External NAT" \ --address=YOUR_STATIC_IP
💡 Tip: A static IP ensures your DNS records remain valid even if the VM is stopped/restarted. Without it, the ephemeral IP may change.
3

SSH into the VM

# SSH via gcloud (easiest — handles SSH keys automatically) gcloud compute ssh minusnow-app --zone=asia-south1-a # Or use standard SSH with your key ssh -i ~/.ssh/your-key username@YOUR_STATIC_IP
💡 Console Alternative: Go to Compute EngineVM InstancesCreate Instance → fill in the same parameters above using the UI. Click the SSH button in the instances list to connect directly from your browser.

5) Install Node.js & Dependencies

All commands below run inside the VM (after SSH).

1

Update system packages

sudo apt update && sudo apt upgrade -y
2

Install Node.js 20.x LTS (via NodeSource)

# Install NodeSource repository curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - # Install Node.js sudo apt install -y nodejs # Verify installation node --version # v20.x.x npm --version # 10.x.x
3

Install essential build tools

sudo apt install -y build-essential git curl wget unzip
4

Install PM2 (process manager)

sudo npm install -g pm2

6) Deploy MinusNow Application (Git Clone from GitHub)

💡 Deployment Model: Since your code is pushed to GitHub, we will clone your repository directly on the VM. Later in Step 8½, we set up GitHub Actions so every git push to main automatically deploys to this VM.
1

Create a dedicated deploy user

Run as root or your admin user. A dedicated deploy user isolates the app from system-level access.

# Create user with home directory sudo useradd -m -s /bin/bash deploy # Create application directory sudo mkdir -p /opt/minusnow sudo chown deploy:deploy /opt/minusnow
2

Generate SSH key for GitHub access (if private repo)

If your repo is private, you need a deploy key so the VM can pull code.

# Switch to deploy user sudo -u deploy -i # Generate an SSH key for GitHub ssh-keygen -t ed25519 -C "deploy@minusnow-gcp" -f ~/.ssh/github_deploy -N "" # Show the public key cat ~/.ssh/github_deploy.pub

Add as Deploy Key on GitHub:

  1. Go to your GitHub repo → SettingsDeploy keysAdd deploy key
  2. Title: GCP VM Deploy Key
  3. Paste the public key
  4. Check "Allow write access" (needed for git pull by Actions)
  5. Click Add key

Configure SSH to use this key for GitHub:

# Create SSH config cat > ~/.ssh/config <<'EOF' Host github.com HostName github.com User git IdentityFile ~/.ssh/github_deploy IdentitiesOnly yes EOF chmod 600 ~/.ssh/config
💡 Public repo? Skip this step entirely — you can clone via HTTPS without authentication.
3

Clone the repository

# As the deploy user sudo -u deploy -i cd /opt/minusnow # Clone via SSH (private repo) git clone git@github.com:YOUR_USERNAME/MinusNow.git . # OR clone via HTTPS (public repo) git clone https://github.com/YOUR_USERNAME/MinusNow.git .
⚠️ Replace YOUR_USERNAME with your actual GitHub username or organization name.
4

Install dependencies & build

cd /opt/minusnow # Install production dependencies npm install # Build the application (compiles TypeScript + bundles client) npm run build # Push database schema npm run db:push
5

Configure environment variables

# Generate a secure session secret SESSION_SECRET=$(openssl rand -hex 32) # Create environment file cat > /opt/minusnow/.env <<EOF NODE_ENV=production PORT=5000 HOST=0.0.0.0 SESSION_SECRET=$SESSION_SECRET DATABASE_URL=file:./data/minusnow.db EOF # Restrict permissions (only deploy user can read) chmod 600 /opt/minusnow/.env
⚠️ Important: The .env file is NOT in your git repo (it's in .gitignore). Generate a unique SESSION_SECRET for each environment. Never commit secrets to GitHub.
6

Create required data directories

mkdir -p /opt/minusnow/data mkdir -p /opt/minusnow/audit-logs chmod 700 /opt/minusnow/data
7

Test the application

# Quick test — start the app directly npm run start # You should see: # MinusNow server listening on port 5000 # Verify from another terminal curl http://localhost:5000 # Press Ctrl+C to stop after confirming it works
✅ Success: If the app responds, your GitHub code is running on GCP. Next we'll set up PM2 for production, and then GitHub Actions for automatic deployments.

7) Database Setup

Option A — SQLite (Simple / Dev)

MinusNow uses SQLite by default with file-based JSON storage. This is the easiest option and works well for small-to-medium deployments.

  • No separate database server needed
  • Data stored in /opt/minusnow/data/
  • Suitable for up to ~500 monitored nodes
  • Back up by copying the data directory
# No extra setup needed — just ensure the data directory exists mkdir -p /opt/minusnow/data chmod 700 /opt/minusnow/data

Option B — Cloud SQL PostgreSQL (Production)

For high availability and larger deployments, use a managed Cloud SQL PostgreSQL instance.

# Create a Cloud SQL PostgreSQL instance gcloud sql instances create minusnow-db \ --database-version=POSTGRES_15 \ --tier=db-custom-2-8192 \ --region=asia-south1 \ --storage-size=20GB \ --storage-type=SSD \ --availability-type=REGIONAL \ --backup-start-time=02:00 \ --enable-bin-log # Set a root password gcloud sql users set-password postgres \ --instance=minusnow-db \ --password=YOUR_SECURE_PASSWORD # Create the application database gcloud sql databases create minusnow \ --instance=minusnow-db

Update the .env file to use PostgreSQL:

DATABASE_URL=postgresql://postgres:YOUR_SECURE_PASSWORD@/minusnow?host=/cloudsql/minusnow-prod:asia-south1:minusnow-db
💡 Use Cloud SQL Auth Proxy for secure connections (see GCP docs).

8) Process Manager (PM2) & Auto-Start

PM2 keeps MinusNow running, auto-restarts on crash, and starts on boot.

1

Start the application with PM2

cd /opt/minusnow # Start with PM2 pm2 start dist/index.cjs --name minusnow \ --max-memory-restart 1G \ --env production # Check it's running pm2 status
2

Enable startup on boot

# Generate the startup script pm2 startup systemd # Copy and run the command PM2 outputs (it will look like): sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u $USER --hp /home/$USER # Save the current PM2 process list pm2 save
3

Useful PM2 commands

pm2 status # View all processes pm2 logs minusnow # View real-time logs pm2 restart minusnow # Restart the app pm2 stop minusnow # Stop the app pm2 monit # CPU/memory monitor pm2 reload minusnow # Zero-downtime restart

8½) GitHub Actions CI/CD — Auto-Deploy on Push

🚀 How it works: Your repo already has a .github/workflows/deploy.yml workflow. When you git push to main, GitHub Actions will: 1) Type-check & build in CI, 2) SSH into your GCP VM, 3) Pull latest code, install, build, and restart PM2 — fully automatic.

Step A — Create SSH Key for GitHub Actions

GitHub Actions needs SSH access to your VM. Create a dedicated key pair.

1

Generate the key pair on the VM

# SSH into the VM gcloud compute ssh minusnow-app --zone=asia-south1-a # Switch to deploy user sudo -u deploy -i # Generate a key pair for GitHub Actions ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions_key -N "" # Add the public key to authorized_keys cat ~/.ssh/github_actions_key.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # Display the PRIVATE key — you'll copy this to GitHub cat ~/.ssh/github_actions_key
🚨 Keep this private key secret. Only paste it into GitHub Secrets (encrypted). Do not commit it to your repo or share it anywhere else.

Step B — Configure GitHub Repository Secrets

Go to your GitHub repo → SettingsSecrets and variablesActionsNew repository secret:

Secret NameValueDescription
SERVER_HOSTYour VM's static IP addressThe external IP from Step 4
SSH_PRIVATE_KEYContents of github_actions_key (private key)Full key including -----BEGIN and -----END lines

Step by step with screenshots:

  1. Open github.com/YOUR_USERNAME/MinusNow
  2. Click Settings (gear icon at top)
  3. Left sidebar → Secrets and variablesActions
  4. Click New repository secret
  5. Name: SERVER_HOST → Value: your VM IP (e.g. 34.xxx.xxx.xxx) → Add secret
  6. Click New repository secret again
  7. Name: SSH_PRIVATE_KEY → Paste the entire private key → Add secret

Step C — Create GitHub Environment

The deploy workflow uses a production environment for protection rules.

  1. Go to your repo → SettingsEnvironments
  2. Click New environment
  3. Name: production → Click Configure environment
  4. (Optional) Add protection rules — require reviewers before deploying, or restrict to main branch
  5. Click Save protection rules

Step D — Your Existing Workflow (already in repo)

Your repo includes .github/workflows/deploy.yml which does the following:

🔨 Job 1: Build & Verify

  • Checks out code
  • Installs Node.js 20 + npm dependencies
  • Runs TypeScript type-check (npm run check)
  • Builds the application (npm run build)
  • Verifies dist/index.cjs and dist/public/ exist

🚀 Job 2: Deploy to Production

  • SSHs into your GCP VM as deploy user
  • Runs git pull origin main
  • Runs npm install
  • Runs npm run build
  • Runs npm run db:push (schema migration)
  • Runs pm2 restart minusnow
# The workflow file (.github/workflows/deploy.yml): name: Deploy MinusNow on: push: branches: [main] # Triggers on every push to main workflow_dispatch: # Also allows manual trigger from GitHub UI jobs: build-test: name: Build & Verify runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20, cache: npm } - run: npm ci - run: npm run check - run: npm run build - run: | test -f dist/index.cjs && echo "Server bundle OK" test -d dist/public && echo "Client build OK" deploy: name: Deploy to Production needs: build-test runs-on: ubuntu-latest environment: production steps: - name: Deploy via SSH uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | set -e cd /opt/minusnow git pull origin main npm install npm run build npm run db:push pm2 restart minusnow sleep 3 pm2 status minusnow

Step E — Test the Full Pipeline

1

Trigger a deploy from your local machine

# Make a small change (e.g., update version in package.json) git add . git commit -m "chore: test GCP deployment pipeline" git push origin main
2

Watch the deployment

Go to github.com/YOUR_USERNAME/MinusNowActions tab → click the running workflow to watch progress.

🟢 Build & Verify — passing 🟢 Deploy to Production — success
3

Or trigger manually from GitHub

Go to ActionsDeploy MinusNowRun workflow → select main branch → Run workflow

4

Verify on the VM

# SSH into the VM and check gcloud compute ssh minusnow-app --zone=asia-south1-a sudo -u deploy pm2 status minusnow sudo -u deploy pm2 logs minusnow --lines 20 curl http://localhost:5000
✅ Done! From now on, every git push to the main branch will automatically: type-check → build → deploy to your GCP VM → restart the app. Zero manual steps needed.

Manual Deploy (if needed)

If you ever need to deploy manually (or GitHub Actions is down):

# SSH into the VM gcloud compute ssh minusnow-app --zone=asia-south1-a # Switch to deploy user and deploy sudo -u deploy -i cd /opt/minusnow git pull origin main npm install npm run build npm run db:push pm2 restart minusnow pm2 status

9) Nginx Reverse Proxy Setup

Nginx sits in front of MinusNow, handles HTTPS termination, compression, and serves static files efficiently.

1

Install Nginx

sudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx
2

Create Nginx configuration

sudo nano /etc/nginx/sites-available/minusnow

Paste the following configuration:

server { listen 80; server_name your-domain.com www.your-domain.com; # Redirect all HTTP to HTTPS (after SSL is set up) return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.com www.your-domain.com; # SSL certificates (will be filled by Certbot) ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # SSL security settings ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; # 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; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; # Client body size (for file uploads) client_max_body_size 50M; # Proxy to MinusNow app on port 5000 location / { proxy_pass http://127.0.0.1:5000; 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_read_timeout 300s; proxy_connect_timeout 75s; } }
3

Enable the site & test

# Enable the site sudo ln -s /etc/nginx/sites-available/minusnow /etc/nginx/sites-enabled/ # Remove default site sudo rm /etc/nginx/sites-enabled/default # Test configuration syntax sudo nginx -t # nginx: configuration file /etc/nginx/nginx.conf test is successful # Reload Nginx sudo systemctl reload nginx
💡 Initial Setup (before SSL): Comment out the ssl_certificate lines and the HTTPS server block. Use just the HTTP block first with proxy_pass instead of return 301. After setting up Let's Encrypt in the next step, re-enable the full config.

10) Domain & Cloud DNS Configuration

1

Create a Cloud DNS Zone (optional — if using GCP DNS)

# Create a DNS managed zone gcloud dns managed-zones create minusnow-zone \ --dns-name="your-domain.com." \ --description="MinusNow DNS" # Get the nameservers — update these at your domain registrar gcloud dns managed-zones describe minusnow-zone --format="value(nameServers)"
2

Create DNS A records

# Point your domain to the VM's static IP gcloud dns record-sets create your-domain.com. \ --zone=minusnow-zone \ --type=A \ --ttl=300 \ --rrdatas=YOUR_STATIC_IP # Also add www subdomain gcloud dns record-sets create www.your-domain.com. \ --zone=minusnow-zone \ --type=CNAME \ --ttl=300 \ --rrdatas=your-domain.com.
3

If using an external DNS provider (GoDaddy, Cloudflare, Namecheap, etc.)

Simply add an A record pointing to your VM's static IP:

TypeNameValueTTL
A@YOUR_STATIC_IP300
CNAMEwwwyour-domain.com300
4

Verify DNS propagation

# Check if DNS is resolving nslookup your-domain.com dig your-domain.com +short # Or use an online checker: dnschecker.org

DNS changes can take up to 48 hours to propagate globally, but typically 5–30 minutes.

11) SSL/TLS Certificate (Let's Encrypt)

Free, auto-renewing SSL certificates via Let's Encrypt and Certbot.

1

Install Certbot

sudo apt install -y certbot python3-certbot-nginx
2

Obtain the SSL certificate

# Certbot will auto-configure Nginx for SSL sudo certbot --nginx -d your-domain.com -d www.your-domain.com # Follow the prompts: # - Enter your email # - Agree to terms # - Choose to redirect HTTP → HTTPS (recommended)
3

Verify auto-renewal

# Test renewal process sudo certbot renew --dry-run # Certbot installs a cron/systemd timer automatically sudo systemctl list-timers | grep certbot
4

Test your SSL

# Quick test curl -I https://your-domain.com # Check SSL grade at: ssllabs.com/ssltest
✅ After this step: Your MinusNow instance is accessible at https://your-domain.com with a valid SSL certificate.

12) Cloud Load Balancer (Optional — Production)

For production deployments needing high availability, use a GCP HTTPS Load Balancer instead of direct SSL on Nginx. This provides DDoS protection, Google-managed SSL certificates, and CDN caching.

1

Create an instance group

# Create an unmanaged instance group gcloud compute instance-groups unmanaged create minusnow-ig \ --zone=asia-south1-a # Add the VM to the group gcloud compute instance-groups unmanaged add-instances minusnow-ig \ --zone=asia-south1-a \ --instances=minusnow-app # Set the named port gcloud compute instance-groups set-named-ports minusnow-ig \ --named-ports=http:5000 \ --zone=asia-south1-a
2

Create health check

gcloud compute health-checks create http minusnow-health-check \ --port=5000 \ --request-path=/ \ --check-interval=30s \ --timeout=10s \ --healthy-threshold=2 \ --unhealthy-threshold=3
3

Create backend service + URL map + HTTPS proxy

# Backend service gcloud compute backend-services create minusnow-backend \ --protocol=HTTP \ --port-name=http \ --health-checks=minusnow-health-check \ --global gcloud compute backend-services add-backend minusnow-backend \ --instance-group=minusnow-ig \ --instance-group-zone=asia-south1-a \ --global # URL map gcloud compute url-maps create minusnow-lb \ --default-service=minusnow-backend # Google-managed SSL certificate gcloud compute ssl-certificates create minusnow-cert \ --domains=your-domain.com,www.your-domain.com \ --global # HTTPS target proxy gcloud compute target-https-proxies create minusnow-https-proxy \ --ssl-certificates=minusnow-cert \ --url-map=minusnow-lb # Global forwarding rule (reserves public IP) gcloud compute forwarding-rules create minusnow-https-rule \ --global \ --target-https-proxy=minusnow-https-proxy \ --ports=443
4

Add firewall rule for GCP health checks

gcloud compute firewall-rules create allow-health-check \ --network=minusnow-vpc \ --allow=tcp:5000 \ --source-ranges=130.211.0.0/22,35.191.0.0/16 \ --target-tags=minusnow-server
5

Update DNS to point to the load balancer IP

# Get the LB's IP address gcloud compute forwarding-rules describe minusnow-https-rule \ --global --format="value(IPAddress)" # Update your DNS A record to this IP instead of the VM IP
💡 When using Load Balancer: You can skip the Nginx SSL setup (Step 11). The LB handles TLS termination. Nginx still runs internally as a reverse proxy on port 80 → 5000, but without SSL.

13) Monitoring & Logging (Cloud Operations)

Cloud Monitoring

GCP Cloud Monitoring (formerly Stackdriver) provides real-time VM and application metrics.

# Install the Ops Agent for enhanced monitoring curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh sudo bash add-google-cloud-ops-agent-repo.sh --also-install # Verify the agent is running sudo systemctl status google-cloud-ops-agent

Set Up Uptime Checks

Go to Cloud ConsoleMonitoringUptime ChecksCreate:

  • Protocol: HTTPS
  • Hostname: your-domain.com
  • Path: /
  • Check frequency: 1 minute
  • Alert: Email + SMS on failure

Cloud Logging

Application logs are automatically collected by the Ops Agent. Configure PM2 to output structured logs:

# View real-time app logs pm2 logs minusnow --lines 100 # View in Cloud Console: # Logging → Logs Explorer → Resource: VM Instance

Create Alert Policies

Go to MonitoringAlertingCreate Policy:

  • CPU utilization > 80% for 5 minutes
  • Memory utilization > 85% for 5 minutes
  • Disk utilization > 90%
  • Uptime check failure (site down)

Set up a notification channel (email, Slack, PagerDuty, SMS) for alerts.

14) Backup & Disaster Recovery

VM Disk Snapshots (Automated)

# Create a snapshot schedule (daily, keep 7 days) gcloud compute resource-policies create snapshot-schedule minusnow-daily-backup \ --region=asia-south1 \ --max-retention-days=7 \ --on-source-disk-delete=keep-auto-snapshots \ --daily-schedule \ --start-time=03:00 # Attach the schedule to the VM's disk gcloud compute disks add-resource-policies minusnow-app \ --resource-policies=minusnow-daily-backup \ --zone=asia-south1-a

Application Data Backup (to Cloud Storage)

# Create a Cloud Storage bucket gsutil mb -l asia-south1 gs://minusnow-backups-$(date +%Y) # Create a backup script cat > /opt/minusnow/backup.sh <<'EOF' #!/bin/bash TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/tmp/minusnow-backup-$TIMESTAMP" mkdir -p "$BACKUP_DIR" # Backup data directory tar -czf "$BACKUP_DIR/data.tar.gz" -C /opt/minusnow data/ # Backup environment config cp /opt/minusnow/.env "$BACKUP_DIR/" # Upload to Cloud Storage gsutil -m cp -r "$BACKUP_DIR" gs://minusnow-backups-2026/ # Cleanup local temp rm -rf "$BACKUP_DIR" echo "Backup completed: $TIMESTAMP" EOF chmod +x /opt/minusnow/backup.sh # Add to cron (daily at 2 AM) (crontab -l 2>/dev/null; echo "0 2 * * * /opt/minusnow/backup.sh >> /var/log/minusnow-backup.log 2>&1") | crontab -

Restore from Backup

# Restore from Cloud Storage gsutil cp gs://minusnow-backups-2026/minusnow-backup-TIMESTAMP/data.tar.gz /tmp/ # Stop the application pm2 stop minusnow # Extract backup cd /opt/minusnow tar -xzf /tmp/data.tar.gz # Restart the application pm2 start minusnow

15) Security Hardening

🔒 OS-Level Hardening

# 1. Disable root SSH login sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sudo systemctl restart sshd # 2. Enable automatic security updates sudo apt install -y unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # 3. Set up UFW firewall (extra layer) sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable # 4. Install fail2ban (brute-force protection) sudo apt install -y fail2ban sudo systemctl enable fail2ban sudo systemctl start fail2ban

🛡️ Application-Level Security

  • Run the app as a non-root user
  • Set NODE_ENV=production (disables debug output)
  • Use strong SESSION_SECRET (256-bit random)
  • Keep Node.js and npm packages updated
  • Restrict firewall to only required ports
  • Enable HTTPS-only with HSTS header
  • Use GCP Secret Manager for sensitive values

Store Secrets in Secret Manager

# Create a secret echo -n "your-session-secret" | \ gcloud secrets create session-secret \ --replication-policy="automatic" \ --data-file=- # Access the secret in your app gcloud secrets versions access latest \ --secret="session-secret"

GCP IAM Best Practices

PrincipleAction
Least privilegeUse custom roles with only required permissions
Service accountsCreate a dedicated service account for the VM (minusnow-sa@project.iam)
No user keysUse IAP for SSH instead of storing SSH keys on the VM
Audit loggingEnable Cloud Audit Logs for all admin activity
Two-factor authEnforce 2-step verification on all Google accounts

16) Go-Live Checklist

ItemDetails
GCP project createdBilling linked, APIs enabled
VPC + firewall configuredSSH restricted, 80/443 open, 5000 internal
VM runningStatic IP assigned, correct machine type
Node.js installedv20.x LTS with npm
App deployedGit cloned, built, .env configured, tested on port 5000
PM2 runningAuto-restart, startup on boot, pm2 save done
GitHub Actions configuredSecrets set (SERVER_HOST, SSH_PRIVATE_KEY), production environment created
CI/CD pipeline testedgit push to main triggers auto-deploy, workflow passes green
Nginx configuredReverse proxy, compression, security headers
Domain pointingA record → static IP, DNS propagated
SSL activeLet's Encrypt or Google-managed, auto-renewal tested
MonitoringOps Agent installed, uptime check, alert policies
BackupsDaily snapshots, Cloud Storage backup script, tested restore
SecurityRoot login disabled, fail2ban, UFW, secrets in Secret Manager
Test everythingHTTPS works, app loads, login works, agents connect

17) Cost Estimation

Monthly Cost Breakdown (asia-south1 — Mumbai)

ResourceSpecificationEst. Monthly Cost
Compute Engine VMe2-standard-2 (2 vCPU, 8 GB)~$49/mo
Boot Disk (SSD)60 GB pd-ssd~$6/mo
Static IP1 external IP (while VM running)$0 (free while attached)
Network Egress~50 GB/mo~$6/mo
Cloud DNS1 managed zone$0.20/mo
Cloud Storage (backups)~10 GB~$0.20/mo
Snapshots7 daily snapshots~$1.50/mo
Cloud SQL (if used)db-custom-2-8192~$85/mo
Total (without Cloud SQL)~$63/mo
Total (with Cloud SQL)~$148/mo

Prices are estimates based on GCP pricing as of 2026. Use the GCP Pricing Calculator for exact figures. Free tier includes 1 e2-micro instance.

18) Troubleshooting

🔍 Common Issues

App won't start

pm2 logs minusnow --lines 50 # Check error logs node --version # Ensure Node 20.x cat /opt/minusnow/.env # Verify env configuration

Can't access from browser

# Check if app is listening curl http://localhost:5000 # Check Nginx sudo nginx -t sudo systemctl status nginx # Check firewall rules gcloud compute firewall-rules list --filter="network:minusnow-vpc" # Check VM external IP gcloud compute instances describe minusnow-app \ --zone=asia-south1-a \ --format="value(networkInterfaces[0].accessConfigs[0].natIP)"

🔧 Debug Commands

SSL certificate issues

# Check certificate status sudo certbot certificates # Force renewal sudo certbot renew --force-renewal # Check if ports are open externally nmap -p 80,443 your-domain.com

DNS not resolving

# Check DNS propagation dig your-domain.com @8.8.8.8 +short nslookup your-domain.com 8.8.4.4 # Check Cloud DNS records gcloud dns record-sets list --zone=minusnow-zone

VM performance issues

# Check system resources htop df -h free -m pm2 monit
🚨 Emergency Recovery: If the VM is completely unresponsive, create a new VM from the latest snapshot: gcloud compute instances create minusnow-recovery --source-snapshot=SNAPSHOT_NAME --zone=asia-south1-a