# Production Setup for ERPNext on Azure with Managed Services ## Overview This guide covers production hardening, security best practices, performance optimization, and operational excellence for ERPNext deployed on Azure using managed services. ## 🔒 Security Hardening ### 1. Azure AD Integration ```bash # Source environment variables source ~/erpnext-azure-env.sh # Enable Azure AD authentication for PostgreSQL az postgres flexible-server ad-admin create \ --server-name $DB_SERVER_NAME \ --resource-group $RESOURCE_GROUP \ --display-name "ERPNext DB Admins" \ --object-id $(az ad group show --group "ERPNext-DB-Admins" --query objectId -o tsv) # Create Azure AD users for application az ad user create \ --display-name "ERPNext Service Account" \ --user-principal-name erpnext-service@yourdomain.onmicrosoft.com \ --password "ComplexPassword123!" # Grant database access to Azure AD user PGPASSWORD=$DB_ADMIN_PASSWORD psql \ -h $DB_SERVER_NAME.postgres.database.azure.com \ -U $DB_ADMIN_USER \ -d erpnext \ -c "CREATE USER \"erpnext-service@yourdomain.onmicrosoft.com\" WITH LOGIN IN ROLE azure_ad_user;" ``` ### 2. Network Security Hardening ```bash # Enable Azure Firewall az network firewall create \ --name erpnext-firewall \ --resource-group $RESOURCE_GROUP \ --location $LOCATION # Create firewall policy az network firewall policy create \ --name erpnext-fw-policy \ --resource-group $RESOURCE_GROUP # Add application rules az network firewall policy rule-collection-group create \ --name erpnext-rules \ --policy-name erpnext-fw-policy \ --resource-group $RESOURCE_GROUP \ --priority 100 # Configure DDoS protection az network ddos-protection create \ --resource-group $RESOURCE_GROUP \ --name erpnext-ddos \ --location $LOCATION az network vnet update \ --resource-group $RESOURCE_GROUP \ --name erpnext-vnet \ --ddos-protection erpnext-ddos ``` ### 3. Web Application Firewall (WAF) ```bash # Create WAF policy az network application-gateway waf-policy create \ --name erpnext-waf-policy \ --resource-group $RESOURCE_GROUP # Configure WAF rules az network application-gateway waf-policy managed-rule managed-rule-set add \ --policy-name erpnext-waf-policy \ --resource-group $RESOURCE_GROUP \ --type OWASP \ --version 3.2 # Enable custom rules for ERPNext az network application-gateway waf-policy custom-rule create \ --name BlockSQLInjection \ --policy-name erpnext-waf-policy \ --resource-group $RESOURCE_GROUP \ --priority 10 \ --rule-type MatchRule \ --action Block \ --match-condition "RequestBody Contains 'SELECT * FROM'" \ --match-condition "RequestBody Contains 'DROP TABLE'" # Apply WAF policy to Application Gateway az network application-gateway update \ --name erpnext-ag \ --resource-group $RESOURCE_GROUP \ --waf-policy erpnext-waf-policy ``` ### 4. Encryption and Key Management ```bash # Enable encryption at host for AKS nodes az aks nodepool update \ --cluster-name erpnext-aks \ --name nodepool1 \ --resource-group $RESOURCE_GROUP \ --enable-encryption-at-host # Configure customer-managed keys for database az keyvault key create \ --vault-name $KEYVAULT_NAME \ --name postgres-cmk \ --kty RSA \ --size 2048 az postgres flexible-server update \ --name $DB_SERVER_NAME \ --resource-group $RESOURCE_GROUP \ --key-vault-key-uri https://$KEYVAULT_NAME.vault.azure.net/keys/postgres-cmk # Enable TDE for database az postgres flexible-server parameter set \ --server-name $DB_SERVER_NAME \ --resource-group $RESOURCE_GROUP \ --name azure.enable_tde \ --value on ``` ## 📊 Monitoring and Observability ### 1. Comprehensive Monitoring Setup ```bash # Create Action Group for alerts az monitor action-group create \ --name erpnext-alerts \ --resource-group $RESOURCE_GROUP \ --short-name ERPAlert \ --email-receiver admin-email --email-address admin@yourdomain.com \ --sms-receiver admin-sms --country-code 1 --phone-number 5551234567 # Database monitoring alerts az monitor metrics alert create \ --name db-high-cpu \ --resource-group $RESOURCE_GROUP \ --scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DBforPostgreSQL/flexibleServers/$DB_SERVER_NAME \ --condition "avg cpu_percent > 80" \ --window-size 5m \ --evaluation-frequency 1m \ --action-group erpnext-alerts az monitor metrics alert create \ --name db-storage-full \ --resource-group $RESOURCE_GROUP \ --scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DBforPostgreSQL/flexibleServers/$DB_SERVER_NAME \ --condition "avg storage_percent > 90" \ --window-size 5m \ --evaluation-frequency 5m \ --action-group erpnext-alerts # Redis monitoring alerts az monitor metrics alert create \ --name redis-high-memory \ --resource-group $RESOURCE_GROUP \ --scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Cache/Redis/$REDIS_NAME \ --condition "avg used_memory_percentage > 90" \ --window-size 5m \ --evaluation-frequency 1m \ --action-group erpnext-alerts # Application monitoring (AKS) az monitor metrics alert create \ --name aks-node-not-ready \ --resource-group $RESOURCE_GROUP \ --scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerService/managedClusters/erpnext-aks \ --condition "avg node_status_condition{condition='Ready',status='false'} > 0" \ --window-size 5m \ --evaluation-frequency 1m \ --action-group erpnext-alerts ``` ### 2. Log Analytics Queries ```bash # Create saved queries for common investigations cat > log-queries.json < 1000 | project TimeGenerated, query_text_s, duration_ms | order by duration_ms desc" }, { "name": "Failed Login Attempts", "query": "ContainerInstanceLog_CL | where Message contains 'Failed login' | summarize FailedAttempts=count() by bin(TimeGenerated, 1h), UserName=extract('user: ([^,]+)', 1, Message)" }, { "name": "API Response Times", "query": "ContainerInstanceLog_CL | where Message contains 'api' | extend ResponseTime=todouble(extract('response_time: ([0-9.]+)', 1, Message)) | summarize avg(ResponseTime), percentile(ResponseTime, 95) by bin(TimeGenerated, 5m)" } ] EOF # Save queries to Log Analytics for query in $(cat log-queries.json | jq -c '.[]'); do name=$(echo $query | jq -r '.name') q=$(echo $query | jq -r '.query') az monitor log-analytics workspace saved-search create \ --workspace-name erpnext-logs \ --resource-group $RESOURCE_GROUP \ --name "$name" \ --category "ERPNext" \ --display-name "$name" \ --query "$q" \ --fa "erpnext" done ``` ### 3. Application Performance Monitoring ```bash # Configure Application Insights for ERPNext cat > appinsights-config.yaml < test-backup.ps1 <<'EOF' param( [string]$ResourceGroup, [string]$ServerName, [string]$BackupName ) # Restore database to test server $testServer = "$ServerName-test" Restore-AzPostgreSqlFlexibleServerDatabase ` -ResourceGroupName $ResourceGroup ` -ServerName $testServer ` -DatabaseName "erpnext-test" ` -BackupName $BackupName # Run validation queries $connection = "Host=$testServer.postgres.database.azure.com;Database=erpnext-test;Username=testuser;Password=$env:DB_PASSWORD" $result = Invoke-Sqlcmd -Query "SELECT COUNT(*) FROM tabUser" -ConnectionString $connection if ($result.Count -gt 0) { Write-Output "Backup validation successful" # Delete test server Remove-AzPostgreSqlFlexibleServer ` -ResourceGroupName $ResourceGroup ` -ServerName $testServer ` -Force } else { throw "Backup validation failed" } EOF # Create Azure Automation account az automation account create \ --name erpnext-automation \ --resource-group $RESOURCE_GROUP # Upload runbook az automation runbook create \ --automation-account-name erpnext-automation \ --resource-group $RESOURCE_GROUP \ --name TestBackup \ --type PowerShell \ --content @test-backup.ps1 ``` ## 🔐 Compliance and Governance ### 1. Azure Policy Implementation ```bash # Create custom policies for ERPNext cat > erpnext-policies.json < capacity-workbook.json < azure-pipelines.yml < slos.yaml < rolling-update.sh <<'EOF' #!/bin/bash set -e echo "Starting rolling update..." # Scale up before update kubectl scale deployment/erpnext-backend --replicas=6 -n erpnext kubectl scale deployment/erpnext-frontend --replicas=4 -n erpnext # Wait for scale up kubectl wait --for=condition=available --timeout=300s deployment/erpnext-backend -n erpnext # Perform rolling update kubectl set image deployment/erpnext-backend backend=$ACR_LOGIN_SERVER/erpnext:$NEW_VERSION -n erpnext kubectl set image deployment/erpnext-frontend frontend=$ACR_LOGIN_SERVER/erpnext-nginx:$NEW_VERSION -n erpnext # Monitor rollout kubectl rollout status deployment/erpnext-backend -n erpnext kubectl rollout status deployment/erpnext-frontend -n erpnext # Scale back down kubectl scale deployment/erpnext-backend --replicas=3 -n erpnext kubectl scale deployment/erpnext-frontend --replicas=2 -n erpnext echo "Rolling update completed successfully" EOF chmod +x rolling-update.sh ``` ## ✅ Production Readiness Checklist - [ ] **Security** - [ ] Azure AD authentication enabled - [ ] Network security groups configured - [ ] WAF enabled and configured - [ ] Encryption at rest and in transit - [ ] Key Vault integration - [ ] Regular security scanning - [ ] **Monitoring** - [ ] All critical metrics monitored - [ ] Alert rules configured - [ ] Dashboards created - [ ] Log aggregation setup - [ ] APM configured - [ ] **Backup & DR** - [ ] Automated backups configured - [ ] Backup testing automated - [ ] DR site configured - [ ] RTO/RPO documented - [ ] Failover procedures tested - [ ] **Performance** - [ ] Database optimized - [ ] Caching configured - [ ] CDN enabled - [ ] Auto-scaling configured - [ ] Load testing completed - [ ] **Operational** - [ ] CI/CD pipeline setup - [ ] Documentation complete - [ ] Runbooks created - [ ] On-call rotation established - [ ] Incident response plan --- **📝 Next Steps**: 1. Review and customize configurations for your specific requirements 2. Conduct security assessment and penetration testing 3. Perform load testing and capacity planning 4. Train operations team on procedures 5. Schedule regular disaster recovery drills