๐ Table of Contents
๐ Overview
This guide covers deploying MinusNow ITSM on Windows Server instances in major cloud platforms. Windows deployment leverages IIS with URL Rewrite and Application Request Routing (ARR) for reverse proxy functionality.
Architecture Overview
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Cloud Load Balancer / โ
โ Azure Application Gateway โ
โ SSL Termination โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Windows VM โโ Windows VM โโ Windows VM โ
โ IIS + ARR โโ IIS + ARR โโ IIS + ARR โ
โ Node.js โโ Node.js โโ Node.js โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Managed PostgreSQL Database โ
โ (Azure / RDS / Cloud SQL) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Cloud Provider Comparison
| Feature | Azure | AWS | GCP |
|---|---|---|---|
| Windows VM | Azure VM | EC2 | Compute Engine |
| Managed DB | Azure PostgreSQL | RDS PostgreSQL | Cloud SQL |
| Load Balancer | Application Gateway | ALB | HTTP(S) LB |
| Best For | Enterprise Windows | Hybrid workloads | ML integration |
๐ป Instance Requirements
Development/Small
| Instance | Standard_D8s_v5 / m6i.2xlarge |
| vCPU | 8 |
| RAM | 32 GB |
| Storage | 500 GB Premium SSD v2 |
| OS | Windows Server 2022 |
Production (Standard)
| Instance | Standard_D16s_v5 / m6i.4xlarge |
| vCPU | 16 |
| RAM | 64 GB |
| Storage | 1 TB Premium SSD v2 |
| OS | Windows Server 2022 |
Production (HA)
| Instances | 3+ VMs (Availability Sets) |
| RAM per VM | 64-128 GB |
| Database | Managed PostgreSQL HA (GP_Gen5_8+) |
| Load Balancer | App Gateway v2 / ALB |
Azure Azure Deployment
1Create Resource Group and VNet
# Login to Azure
az login
# Create resource group
az group create --name minusnow-rg --location eastus
# Create VNet with subnets
az network vnet create `
--resource-group minusnow-rg `
--name minusnow-vnet `
--address-prefix 10.0.0.0/16
az network vnet subnet create `
--resource-group minusnow-rg `
--vnet-name minusnow-vnet `
--name app-subnet `
--address-prefix 10.0.1.0/24
az network vnet subnet create `
--resource-group minusnow-rg `
--vnet-name minusnow-vnet `
--name db-subnet `
--address-prefix 10.0.2.0/24
2Create Azure Database for PostgreSQL
# Create PostgreSQL Flexible Server
az postgres flexible-server create `
--resource-group minusnow-rg `
--name minusnow-itsm-db `
--location eastus `
--admin-user minusnowadmin `
--admin-password YourSecurePassword123! `
--sku-name Standard_B2s `
--tier Burstable `
--storage-size 128 `
--version 15 `
--high-availability ZoneRedundant
# Create database
az postgres flexible-server db create `
--resource-group minusnow-rg `
--server-name minusnow-itsm-db `
--database-name minusnow_itsm
# Allow Azure services
az postgres flexible-server firewall-rule create `
--resource-group minusnow-rg `
--name minusnow-itsm-db `
--rule-name AllowAzureServices `
--start-ip-address 0.0.0.0 `
--end-ip-address 0.0.0.0
3Create Windows Server VM
# Create Network Security Group
az network nsg create --resource-group minusnow-rg --name minusnow-nsg
# Allow RDP, HTTP, HTTPS
az network nsg rule create --resource-group minusnow-rg --nsg-name minusnow-nsg `
--name AllowRDP --priority 1000 --destination-port-ranges 3389 --access Allow
az network nsg rule create --resource-group minusnow-rg --nsg-name minusnow-nsg `
--name AllowHTTP --priority 1100 --destination-port-ranges 80 --access Allow
az network nsg rule create --resource-group minusnow-rg --nsg-name minusnow-nsg `
--name AllowHTTPS --priority 1200 --destination-port-ranges 443 --access Allow
# Create Windows Server 2022 VM
az vm create `
--resource-group minusnow-rg `
--name minusnow-vm `
--image MicrosoftWindowsServer:WindowsServer:2022-datacenter-azure-edition:latest `
--size Standard_D4s_v3 `
--admin-username minusnowadmin `
--admin-password YourVMPassword123! `
--vnet-name minusnow-vnet `
--subnet app-subnet `
--nsg minusnow-nsg `
--public-ip-address minusnow-pip
4Create Application Gateway
# Create subnet for App Gateway
az network vnet subnet create `
--resource-group minusnow-rg `
--vnet-name minusnow-vnet `
--name agw-subnet `
--address-prefix 10.0.3.0/24
# Create Application Gateway with SSL
az network application-gateway create `
--resource-group minusnow-rg `
--name minusnow-agw `
--location eastus `
--sku Standard_v2 `
--capacity 2 `
--vnet-name minusnow-vnet `
--subnet agw-subnet `
--public-ip-address minusnow-agw-pip `
--http-settings-port 80 `
--http-settings-protocol Http `
--frontend-port 443 `
--servers 10.0.1.4
AWS AWS Deployment
1Create VPC and Security Groups
# Create VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=minusnow-vpc}]"
# Create subnets in different AZs
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.2.0/24 --availability-zone us-east-1b
# Create security group for Windows
aws ec2 create-security-group --group-name minusnow-windows-sg --description "MinusNow Windows Server" --vpc-id vpc-xxx
# Allow RDP, HTTP, HTTPS
aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 3389 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 443 --cidr 0.0.0.0/0
2Create RDS PostgreSQL
# Create DB subnet group
aws rds create-db-subnet-group `
--db-subnet-group-name minusnow-db-subnet `
--db-subnet-group-description "MinusNow DB Subnets" `
--subnet-ids subnet-xxx subnet-yyy
# Create RDS PostgreSQL instance
aws rds create-db-instance `
--db-instance-identifier minusnow-itsm-db `
--db-instance-class db.t3.medium `
--engine postgres `
--engine-version 15.4 `
--master-username minusnowadmin `
--master-user-password YourSecurePassword123! `
--allocated-storage 100 `
--storage-type gp3 `
--multi-az `
--backup-retention-period 7 `
--storage-encrypted `
--db-name minusnow_itsm
3Launch Windows Server EC2
# Get latest Windows Server 2022 AMI
$AMI_ID = aws ec2 describe-images `
--owners amazon `
--filters "Name=name,Values=Windows_Server-2022-English-Full-Base-*" `
--query "sort_by(Images, &CreationDate)[-1].ImageId" `
--output text
# Launch EC2 instance
aws ec2 run-instances `
--image-id $AMI_ID `
--instance-type t3.xlarge `
--key-name minusnow-key `
--security-group-ids sg-xxx `
--subnet-id subnet-xxx `
--associate-public-ip-address `
--block-device-mappings "[{\"DeviceName\":\"/dev/sda1\",\"Ebs\":{\"VolumeSize\":256,\"VolumeType\":\"gp3\",\"Encrypted\":true}}]" `
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=minusnow-itsm-windows}]"
GCP Google Cloud Deployment
1Create VPC and Firewall
# Set project
gcloud config set project your-project-id
# Create VPC
gcloud compute networks create minusnow-vpc --subnet-mode=custom
# Create subnet
gcloud compute networks subnets create minusnow-subnet \
--network=minusnow-vpc \
--region=us-central1 \
--range=10.0.1.0/24
# Create firewall rules
gcloud compute firewall-rules create minusnow-allow-rdp \
--network=minusnow-vpc \
--allow=tcp:3389 \
--source-ranges=0.0.0.0/0
gcloud compute firewall-rules create minusnow-allow-web \
--network=minusnow-vpc \
--allow=tcp:80,tcp:443 \
--source-ranges=0.0.0.0/0
2Create Cloud SQL PostgreSQL
# Create Cloud SQL instance
gcloud sql instances create minusnow-itsm-db \
--database-version=POSTGRES_15 \
--tier=db-custom-2-4096 \
--region=us-central1 \
--storage-type=SSD \
--storage-size=100GB \
--availability-type=REGIONAL
# Create database
gcloud sql databases create minusnow_itsm --instance=minusnow-itsm-db
# Create user
gcloud sql users create minusnowadmin \
--instance=minusnow-itsm-db \
--password=YourSecurePassword123!
3Create Windows Server Instance
# Create Windows Server 2022 instance
gcloud compute instances create minusnow-vm \
--zone=us-central1-a \
--machine-type=e2-standard-4 \
--image-family=windows-2022 \
--image-project=windows-cloud \
--boot-disk-size=256GB \
--boot-disk-type=pd-ssd \
--network=minusnow-vpc \
--subnet=minusnow-subnet
# Reset Windows password
gcloud compute reset-windows-password minusnow-vm --zone=us-central1-a --user=minusnowadmin
๐ IIS Configuration
After connecting to your Windows VM via RDP, configure IIS with URL Rewrite and ARR.
1Install IIS and Features
# Install IIS with all required features
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
Install-WindowsFeature -Name Web-WebSockets
Install-WindowsFeature -Name Web-Url-Auth
Install-WindowsFeature -Name Web-IP-Security
# Install URL Rewrite Module
$url = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi"
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\urlrewrite.msi"
Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\urlrewrite.msi`" /qn" -Wait
# Install Application Request Routing
$url = "https://download.microsoft.com/download/A/0/4/A04DF5F6-0A0B-43C6-B29C-2C6E1E63547D/requestRouter_amd64.msi"
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\arr.msi"
Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\arr.msi`" /qn" -Wait
2Install Node.js
# Download and install Node.js LTS
$nodeUrl = "https://nodejs.org/dist/v20.11.0/node-v20.11.0-x64.msi"
Invoke-WebRequest -Uri $nodeUrl -OutFile "$env:TEMP\nodejs.msi"
Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\nodejs.msi`" /qn" -Wait
# Refresh environment variables
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
# Verify installation
node --version
npm --version
3Deploy Application
# Create application directory
New-Item -ItemType Directory -Path "C:\inetpub\minusnow-itsm" -Force
Set-Location "C:\inetpub\minusnow-itsm"
# Download application package (or copy via SCP/Azure Storage/S3)
# Invoke-WebRequest -Uri "https://your-storage/minusnow-itsm.zip" -OutFile "minusnow-itsm.zip"
# Expand-Archive -Path "minusnow-itsm.zip" -DestinationPath "."
# Install dependencies
npm install --production
# Create .env file
@"
NODE_ENV=production
DATABASE_URL=postgresql://minusnowadmin:password@your-db-host:5432/minusnow_itsm?sslmode=require
SESSION_SECRET=$(New-Guid)
"@ | Out-File -FilePath ".env" -Encoding UTF8
4Configure web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="dist/index.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<!-- Reverse proxy with X-Forwarded headers -->
<rule name="ReverseProxyToNode" stopProcessing="true">
<match url="(.*)" />
<serverVariables>
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
<set name="HTTP_X_FORWARDED_PROTO" value="{HTTPS}" />
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_REAL_IP" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://localhost:5000/{R:1}" />
</rule>
</rules>
<allowedServerVariables>
<add name="HTTP_X_FORWARDED_FOR" />
<add name="HTTP_X_FORWARDED_PROTO" />
<add name="HTTP_X_FORWARDED_HOST" />
<add name="HTTP_X_REAL_IP" />
</allowedServerVariables>
</rewrite>
<proxy enabled="true" preserveHostHeader="true" />
<webSocket enabled="true" />
<iisnode node_env="production" loggingEnabled="true" />
</system.webServer>
</configuration>
X-Forwarded Headers: Required when behind cloud load balancers (ALB, App Gateway, HTTP LB) to pass original client IP and protocol to the application.
5Create Windows Service with NSSM
# Download NSSM
Invoke-WebRequest -Uri "https://nssm.cc/release/nssm-2.24.zip" -OutFile "$env:TEMP\nssm.zip"
Expand-Archive -Path "$env:TEMP\nssm.zip" -DestinationPath "$env:TEMP"
Copy-Item "$env:TEMP\nssm-2.24\win64\nssm.exe" -Destination "C:\Windows\System32"
# Install service
nssm install MinusNowITSM "C:\Program Files\nodejs\node.exe"
nssm set MinusNowITSM AppParameters "dist/index.js"
nssm set MinusNowITSM AppDirectory "C:\inetpub\minusnow-itsm"
nssm set MinusNowITSM AppEnvironmentExtra "NODE_ENV=production"
nssm set MinusNowITSM DisplayName "MinusNow ITSM Service"
nssm set MinusNowITSM Description "MinusNow ITSM Application"
nssm set MinusNowITSM Start SERVICE_AUTO_START
nssm set MinusNowITSM AppStdout "C:\inetpub\minusnow-itsm\logs\stdout.log"
nssm set MinusNowITSM AppStderr "C:\inetpub\minusnow-itsm\logs\stderr.log"
nssm set MinusNowITSM AppRotateFiles 1
nssm set MinusNowITSM AppRotateBytes 10485760
# Start service
nssm start MinusNowITSM
๐๏ธ Managed Database Connection
Connection String Examples
# Azure Database for PostgreSQL
DATABASE_URL=postgresql://minusnowadmin:password@minusnow-itsm-db.postgres.database.azure.com:5432/minusnow_itsm?sslmode=require
# AWS RDS PostgreSQL
DATABASE_URL=postgresql://minusnowadmin:password@minusnow-itsm-db.xxxx.us-east-1.rds.amazonaws.com:5432/minusnow_itsm?sslmode=require
# GCP Cloud SQL (via Cloud SQL Auth Proxy)
DATABASE_URL=postgresql://minusnowadmin:password@localhost:5432/minusnow_itsm
SSL/TLS: Always use
sslmode=require for production database connections to ensure encrypted communication.
๐ SSL/TLS Configuration
Option 1: Let's Encrypt with win-acme
# Download win-acme
Invoke-WebRequest -Uri "https://github.com/win-acme/win-acme/releases/download/v2.2.9/win-acme.v2.2.9.1701.x64.trimmed.zip" -OutFile "$env:TEMP\win-acme.zip"
Expand-Archive -Path "$env:TEMP\win-acme.zip" -DestinationPath "C:\win-acme"
# Run interactive mode
C:\win-acme\wacs.exe
Option 2: Import PFX Certificate
# Import certificate
$password = ConvertTo-SecureString -String "CertPassword" -AsPlainText -Force
Import-PfxCertificate -FilePath "C:\certs\minusnow-itsm.pfx" -CertStoreLocation Cert:\LocalMachine\My -Password $password
# Bind to IIS site
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*itsm.yourdomain.com*"}
New-IISSiteBinding -Name "MinusNowITSM" -BindingInformation "*:443:" -Protocol https -CertificateThumbPrint $cert.Thumbprint -CertStoreLocation "Cert:\LocalMachine\My"
โ๏ธ Load Balancing
Azure App Gateway
- Layer 7 load balancing
- WAF protection
- SSL termination
- Cookie-based affinity
AWS ALB
- Application Load Balancer
- Target groups
- Health checks
- Auto scaling integration
GCP HTTP(S) LB
- Global load balancing
- Cloud Armor WAF
- Managed certificates
- CDN integration
Health Check Endpoint
# Configure health check path: /health
# Response: HTTP 200 OK with JSON body
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z"
}
๐ Monitoring
Azure Monitor
# Install Azure Monitor agent
Invoke-WebRequest -Uri "https://aka.ms/AzureMonitorAgentInstaller" -OutFile "$env:TEMP\AMAInstaller.exe"
Start-Process "$env:TEMP\AMAInstaller.exe" -ArgumentList "/silent" -Wait
AWS CloudWatch
# Install CloudWatch agent
$url = "https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi"
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\cw-agent.msi"
Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\cw-agent.msi`" /qn" -Wait
Windows Event Logging
# Create custom event log source
New-EventLog -LogName Application -Source "MinusNowITSM"
# Write events
Write-EventLog -LogName Application -Source "MinusNowITSM" -EventId 1000 -EntryType Information -Message "Application started"
# View logs
Get-EventLog -LogName Application -Source "MinusNowITSM" -Newest 50
๐ Backup & Recovery
Database Backup
Managed database services provide automated backups:
- Azure: Point-in-time restore up to 35 days
- AWS RDS: Automated snapshots, 35-day retention
- GCP: Automated backups with geo-redundancy
Application Backup
# Backup script
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$backupPath = "C:\Backups\minusnow-$timestamp"
Copy-Item -Path "C:\inetpub\minusnow-itsm\data" -Destination $backupPath -Recurse
# Upload to Azure Blob
azcopy copy $backupPath "https://mystorageaccount.blob.core.windows.net/backups/" --recursive
๐ง Troubleshooting
IIS Issues
# Check IIS logs
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\*.log" -Tail 50
# Reset IIS
iisreset /restart
# Check URL Rewrite rules
Get-WebConfiguration -Filter "/system.webServer/rewrite/rules/*" -PSPath "IIS:\Sites\MinusNowITSM"
Service Issues
# Check service status
Get-Service MinusNowITSM
nssm status MinusNowITSM
# View service logs
Get-Content "C:\inetpub\minusnow-itsm\logs\stderr.log" -Tail 100
# Restart service
Restart-Service MinusNowITSM
Database Connection
# Test PostgreSQL connection
Test-NetConnection -ComputerName "your-db-host" -Port 5432
# Check firewall rules
Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*PostgreSQL*"}
Network Diagnostics
# Check listening ports
netstat -an | findstr "5000"
netstat -an | findstr "80"
netstat -an | findstr "443"
# Test local app
Invoke-WebRequest -Uri "http://localhost:5000/health" -UseBasicParsing