guides. Here's what has been created: 📁 Azure Managed Deployment Structure documentation/deployment-guides/azure-managed/ ├── README.md # Overview and architecture ├── 00-prerequisites-managed.md # Azure prerequisites setup ├── 01-aks-managed-deployment.md # AKS deployment guide ├── 02-container-instances-deployment.md # Container Instances guide ├── 03-production-managed-setup.md # Production hardening ├── kubernetes-manifests/ # Kubernetes YAML files │ ├── namespace.yaml # Namespace and quotas │ ├── storage.yaml # Azure Files storage │ ├── configmap.yaml # Configuration │ ├── secrets.yaml # Key Vault integration │ ├── erpnext-backend.yaml # Backend deployment │ ├── erpnext-frontend.yaml # Frontend deployment │ ├── erpnext-workers.yaml # Workers and scheduler │ ├── ingress.yaml # Ingress configuration │ └── jobs.yaml # Init and backup jobs └── scripts/ # Automation scripts ├── deploy-managed.sh # AKS deployment script └── container-instances-deploy.sh # ACI deployment script 🔄 Key Azure Service Mappings | GCP Service | Azure Equivalent | Notes | |----------------------|--------------------------------------|------------------------------- ---------------------------| | Cloud SQL (MySQL) | Azure Database for PostgreSQL | Using PostgreSQL instead of MySQL for better performance | | Memorystore (Redis) | Azure Cache for Redis | Full compatibility with SSL/TLS | | Cloud Storage | Azure Storage (Blob & Files) | Using Azure Files for shared storage | | GKE | AKS (Azure Kubernetes Service) | Full Kubernetes compatibility | | Cloud Run | Azure Container Instances | Serverless container platform | | Secret Manager | Azure Key Vault | Integrated secrets management | | Cloud IAM | Azure AD & Managed Identity | Enterprise-grade identity | | Stackdriver | Azure Monitor & Application Insights | Comprehensive monitoring | | Cloud CDN | Azure CDN | Global content delivery | | Cloud Load Balancing | Azure Application Gateway | L7 load balancing with WAF | ✨ Key Features 1. Managed Services - Azure Database for PostgreSQL with high availability - Azure Cache for Redis with SSL/TLS encryption - Azure Storage for file management - Azure Key Vault for secrets 2. Deployment Options - AKS: Full Kubernetes control with auto-scaling - Container Instances: Simpler serverless deployment 3. Security - Managed Identity for passwordless authentication - Private endpoints for database and Redis - Network security groups and firewall rules - SSL/TLS encryption everywhere 4. Production Ready - Automated backups with geo-redundancy - Monitoring with Azure Monitor - Auto-scaling configurations - Disaster recovery setup 5. Automation - One-command deployment scripts - Environment validation - Health checks and diagnostics - Backup automation 🚀 Quick Start # Prerequisites cd documentation/deployment-guides/azure-managed/ # Follow 00-prerequisites-managed.md # Option 1: Deploy to AKS ./scripts/deploy-managed.sh deploy # Option 2: Deploy to Container Instances ./scripts/container-instances-deploy.sh deploy 💰 Cost Comparison | Deployment Size | Azure (Monthly) | GCP (Monthly) | |-------------------|-----------------|---------------| | Small (<50 users) | ~ | ~ | | Medium (50-200) | ~ | ~ | | Large (200+) | ~,823 | ~,794 | The Azure deployment uses PostgreSQL instead of MySQL, which provides better performance and features, and includes Azure-specific optimizations for the cloud-native environment.
23 KiB
23 KiB
Azure Container Instances Deployment with Managed Services
Overview
This guide covers deploying ERPNext on Azure Container Instances (ACI) using Azure Database for PostgreSQL and Azure Cache for Redis. ACI provides a serverless container platform ideal for simpler deployments without Kubernetes complexity.
Prerequisites
- Completed all steps in
00-prerequisites-managed.md - Azure CLI installed and configured
- Docker installed locally for image building
- Environment variables from prerequisites exported
🚀 Container Registry Setup
1. Create Azure Container Registry
# Source environment variables
source ~/erpnext-azure-env.sh
# Create container registry
export ACR_NAME="erpnextacr$(openssl rand -hex 4)"
az acr create \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard \
--admin-enabled true
# Get registry credentials
export ACR_LOGIN_SERVER=$(az acr show --name $ACR_NAME --query loginServer -o tsv)
export ACR_USERNAME=$(az acr credential show --name $ACR_NAME --query username -o tsv)
export ACR_PASSWORD=$(az acr credential show --name $ACR_NAME --query passwords[0].value -o tsv)
# Login to registry
az acr login --name $ACR_NAME
2. Build and Push Custom Images
# Create Dockerfile for ERPNext with Azure integrations
cat > Dockerfile.azure <<EOF
FROM frappe/erpnext-worker:v14
# Install Azure Storage SDK for Python
RUN pip install azure-storage-blob azure-identity
# Add custom entrypoint for Azure configurations
COPY entrypoint.azure.sh /entrypoint.azure.sh
RUN chmod +x /entrypoint.azure.sh
ENTRYPOINT ["/entrypoint.azure.sh"]
EOF
# Create entrypoint script
cat > entrypoint.azure.sh <<EOF
#!/bin/bash
set -e
# Configure database connection for PostgreSQL
export DB_TYPE="postgres"
export DB_HOST="\${DB_HOST}"
export DB_PORT="5432"
export DB_NAME="erpnext"
# Configure Redis with authentication
export REDIS_CACHE="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/0?ssl_cert_reqs=required"
export REDIS_QUEUE="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/1?ssl_cert_reqs=required"
export REDIS_SOCKETIO="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/2?ssl_cert_reqs=required"
# Execute original command
exec "\$@"
EOF
# Build and push image
docker build -f Dockerfile.azure -t $ACR_LOGIN_SERVER/erpnext-azure:v14 .
docker push $ACR_LOGIN_SERVER/erpnext-azure:v14
📦 Deploy Container Instances
1. Create File Share for Persistent Storage
# Create file share in storage account
az storage share create \
--name erpnext-sites \
--account-name $STORAGE_ACCOUNT \
--quota 100
az storage share create \
--name erpnext-assets \
--account-name $STORAGE_ACCOUNT \
--quota 50
# Get storage account key
export STORAGE_KEY=$(az storage account keys list \
--account-name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--query "[0].value" -o tsv)
2. Deploy Backend Container Group
# Create backend container group with multiple containers
cat > aci-backend-deployment.yaml <<EOF
apiVersion: 2021-10-01
location: $LOCATION
name: erpnext-backend
properties:
containers:
- name: backend
properties:
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
resources:
requests:
cpu: 2
memoryInGb: 4
ports:
- port: 8000
protocol: TCP
environmentVariables:
- name: DB_HOST
value: $DB_SERVER_NAME.postgres.database.azure.com
- name: DB_USER
value: $DB_ADMIN_USER
- name: DB_PASSWORD
secureValue: $DB_ADMIN_PASSWORD
- name: REDIS_HOST
value: $REDIS_HOST
- name: REDIS_PASSWORD
secureValue: $REDIS_KEY
- name: ADMIN_PASSWORD
secureValue: YourSecurePassword123!
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
- name: assets
mountPath: /home/frappe/frappe-bench/sites/assets
- name: scheduler
properties:
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
command: ["bench", "schedule"]
resources:
requests:
cpu: 0.5
memoryInGb: 1
environmentVariables:
- name: DB_HOST
value: $DB_SERVER_NAME.postgres.database.azure.com
- name: DB_USER
value: $DB_ADMIN_USER
- name: DB_PASSWORD
secureValue: $DB_ADMIN_PASSWORD
- name: REDIS_HOST
value: $REDIS_HOST
- name: REDIS_PASSWORD
secureValue: $REDIS_KEY
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
- name: worker-default
properties:
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
command: ["bench", "worker", "--queue", "default"]
resources:
requests:
cpu: 1
memoryInGb: 2
environmentVariables:
- name: DB_HOST
value: $DB_SERVER_NAME.postgres.database.azure.com
- name: DB_USER
value: $DB_ADMIN_USER
- name: DB_PASSWORD
secureValue: $DB_ADMIN_PASSWORD
- name: REDIS_HOST
value: $REDIS_HOST
- name: REDIS_PASSWORD
secureValue: $REDIS_KEY
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
- name: worker-long
properties:
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
command: ["bench", "worker", "--queue", "long"]
resources:
requests:
cpu: 1
memoryInGb: 2
environmentVariables:
- name: DB_HOST
value: $DB_SERVER_NAME.postgres.database.azure.com
- name: DB_USER
value: $DB_ADMIN_USER
- name: DB_PASSWORD
secureValue: $DB_ADMIN_PASSWORD
- name: REDIS_HOST
value: $REDIS_HOST
- name: REDIS_PASSWORD
secureValue: $REDIS_KEY
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
imageRegistryCredentials:
- server: $ACR_LOGIN_SERVER
username: $ACR_USERNAME
password: $ACR_PASSWORD
volumes:
- name: sites
azureFile:
shareName: erpnext-sites
storageAccountName: $STORAGE_ACCOUNT
storageAccountKey: $STORAGE_KEY
- name: assets
azureFile:
shareName: erpnext-assets
storageAccountName: $STORAGE_ACCOUNT
storageAccountKey: $STORAGE_KEY
osType: Linux
restartPolicy: Always
ipAddress:
type: Private
ports:
- port: 8000
protocol: TCP
subnetIds:
- id: /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet
type: Microsoft.ContainerInstance/containerGroups
EOF
# Deploy backend container group
az container create \
--resource-group $RESOURCE_GROUP \
--file aci-backend-deployment.yaml
3. Deploy Frontend Container Group
# Create frontend container group
cat > aci-frontend-deployment.yaml <<EOF
apiVersion: 2021-10-01
location: $LOCATION
name: erpnext-frontend
properties:
containers:
- name: frontend
properties:
image: frappe/erpnext-nginx:v14
resources:
requests:
cpu: 1
memoryInGb: 1
ports:
- port: 8080
protocol: TCP
environmentVariables:
- name: BACKEND
value: erpnext-backend.internal:8000
- name: FRAPPE_SITE_NAME_HEADER
value: frontend
- name: SOCKETIO
value: erpnext-websocket.internal:9000
- name: UPSTREAM_REAL_IP_ADDRESS
value: 127.0.0.1
- name: UPSTREAM_REAL_IP_HEADER
value: X-Forwarded-For
- name: PROXY_READ_TIMEOUT
value: "120"
- name: CLIENT_MAX_BODY_SIZE
value: 50m
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
- name: assets
mountPath: /usr/share/nginx/html/assets
- name: websocket
properties:
image: frappe/frappe-socketio:v14
resources:
requests:
cpu: 0.5
memoryInGb: 0.5
ports:
- port: 9000
protocol: TCP
volumeMounts:
- name: sites
mountPath: /home/frappe/frappe-bench/sites
imageRegistryCredentials:
- server: $ACR_LOGIN_SERVER
username: $ACR_USERNAME
password: $ACR_PASSWORD
volumes:
- name: sites
azureFile:
shareName: erpnext-sites
storageAccountName: $STORAGE_ACCOUNT
storageAccountKey: $STORAGE_KEY
- name: assets
azureFile:
shareName: erpnext-assets
storageAccountName: $STORAGE_ACCOUNT
storageAccountKey: $STORAGE_KEY
osType: Linux
restartPolicy: Always
ipAddress:
type: Public
ports:
- port: 8080
protocol: TCP
dnsNameLabel: erpnext-$RESOURCE_GROUP
type: Microsoft.ContainerInstance/containerGroups
EOF
# Deploy frontend container group
az container create \
--resource-group $RESOURCE_GROUP \
--file aci-frontend-deployment.yaml
# Get public IP/FQDN
export FRONTEND_FQDN=$(az container show \
--resource-group $RESOURCE_GROUP \
--name erpnext-frontend \
--query ipAddress.fqdn -o tsv)
echo "Frontend accessible at: http://$FRONTEND_FQDN:8080"
🔄 Initialize ERPNext Site
# Run site initialization as a one-time container
az container create \
--resource-group $RESOURCE_GROUP \
--name erpnext-init \
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
--cpu 2 \
--memory 4 \
--restart-policy Never \
--environment-variables \
DB_HOST=$DB_SERVER_NAME.postgres.database.azure.com \
DB_USER=$DB_ADMIN_USER \
REDIS_HOST=$REDIS_HOST \
--secure-environment-variables \
DB_PASSWORD=$DB_ADMIN_PASSWORD \
REDIS_PASSWORD=$REDIS_KEY \
ADMIN_PASSWORD="YourSecurePassword123!" \
--azure-file-volume-account-name $STORAGE_ACCOUNT \
--azure-file-volume-account-key $STORAGE_KEY \
--azure-file-volume-share-name erpnext-sites \
--azure-file-volume-mount-path /home/frappe/frappe-bench/sites \
--command-line "/bin/bash -c 'bench new-site frontend --db-host \$DB_HOST --db-port 5432 --db-name erpnext --db-password \$DB_PASSWORD --admin-password \$ADMIN_PASSWORD --install-app erpnext && bench --site frontend migrate'" \
--subnet /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet \
--registry-login-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD
# Wait for initialization to complete
az container show \
--resource-group $RESOURCE_GROUP \
--name erpnext-init \
--query containers[0].instanceView.currentState.state
# View initialization logs
az container logs \
--resource-group $RESOURCE_GROUP \
--name erpnext-init
# Delete init container after completion
az container delete \
--resource-group $RESOURCE_GROUP \
--name erpnext-init \
--yes
🌐 Configure Application Gateway
# Create public IP for Application Gateway
az network public-ip create \
--resource-group $RESOURCE_GROUP \
--name erpnext-ag-pip \
--allocation-method Static \
--sku Standard
# Create Application Gateway
az network application-gateway create \
--resource-group $RESOURCE_GROUP \
--name erpnext-ag \
--location $LOCATION \
--vnet-name erpnext-vnet \
--subnet aks-subnet \
--public-ip-address erpnext-ag-pip \
--sku Standard_v2 \
--capacity 2 \
--http-settings-port 8080 \
--http-settings-protocol Http \
--frontend-port 80 \
--routing-rule-type Basic
# Configure backend pool with container instances
az network application-gateway address-pool create \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name erpnext-backend-pool \
--servers $FRONTEND_FQDN
# Create health probe
az network application-gateway probe create \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name erpnext-health \
--protocol Http \
--path / \
--interval 30 \
--timeout 30 \
--threshold 3
# Configure SSL (optional)
# Upload SSL certificate
az network application-gateway ssl-cert create \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name erpnext-ssl \
--cert-file /path/to/certificate.pfx \
--cert-password YourCertPassword
# Create HTTPS listener
az network application-gateway frontend-port create \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name https-port \
--port 443
az network application-gateway http-listener create \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name erpnext-https-listener \
--frontend-port https-port \
--ssl-cert erpnext-ssl
📊 Monitoring and Logging
1. Enable Container Insights
# Enable diagnostics for container groups
az monitor diagnostic-settings create \
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
--name erpnext-backend-diagnostics \
--workspace /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/erpnext-logs \
--logs '[{"category": "ContainerInstanceLog", "enabled": true}]' \
--metrics '[{"category": "AllMetrics", "enabled": true}]'
az monitor diagnostic-settings create \
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-frontend \
--name erpnext-frontend-diagnostics \
--workspace /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/erpnext-logs \
--logs '[{"category": "ContainerInstanceLog", "enabled": true}]' \
--metrics '[{"category": "AllMetrics", "enabled": true}]'
2. View Container Logs
# View backend logs
az container logs \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name backend
# View worker logs
az container logs \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name worker-default
# Stream logs
az container attach \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name backend
3. Create Alerts
# Alert for container restart
az monitor metrics alert create \
--name erpnext-container-restart \
--resource-group $RESOURCE_GROUP \
--scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
--condition "sum RestartCount > 5" \
--window-size 15m \
--evaluation-frequency 5m
# Alert for high CPU usage
az monitor metrics alert create \
--name erpnext-high-cpu \
--resource-group $RESOURCE_GROUP \
--scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
--condition "avg CpuUsage > 80" \
--window-size 5m \
--evaluation-frequency 1m
🔧 Scaling Container Instances
Manual Scaling
# Scale by creating additional container groups
for i in {2..3}; do
sed "s/erpnext-backend/erpnext-backend-$i/g" aci-backend-deployment.yaml > aci-backend-deployment-$i.yaml
az container create \
--resource-group $RESOURCE_GROUP \
--file aci-backend-deployment-$i.yaml
done
# Update Application Gateway backend pool
az network application-gateway address-pool update \
--resource-group $RESOURCE_GROUP \
--gateway-name erpnext-ag \
--name erpnext-backend-pool \
--servers erpnext-backend-1.internal erpnext-backend-2.internal erpnext-backend-3.internal
Auto-scaling with Logic Apps
# Create Logic App for auto-scaling
az logic workflow create \
--resource-group $RESOURCE_GROUP \
--name erpnext-autoscale \
--definition '{
"triggers": {
"Recurrence": {
"recurrence": {
"frequency": "Minute",
"interval": 5
},
"type": "Recurrence"
}
},
"actions": {
"CheckMetrics": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/'$RESOURCE_GROUP'/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend/providers/Microsoft.Insights/metrics?api-version=2018-01-01&metricnames=CpuUsage"
}
},
"ScaleDecision": {
"type": "If",
"expression": "@greater(body('"'"'CheckMetrics'"'"').value[0].timeseries[0].data[0].average, 70)",
"actions": {
"ScaleUp": {
"type": "Http",
"inputs": {
"method": "PUT",
"uri": "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/'$RESOURCE_GROUP'/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend-new?api-version=2021-10-01"
}
}
}
}
}
}'
🔄 Backup and Recovery
1. Database Backup
# Manual backup
az postgres flexible-server backup create \
--resource-group $RESOURCE_GROUP \
--server-name $DB_SERVER_NAME \
--backup-name erpnext-manual-backup-$(date +%Y%m%d)
# List backups
az postgres flexible-server backup list \
--resource-group $RESOURCE_GROUP \
--server-name $DB_SERVER_NAME
2. File Storage Backup
# Create backup container
az storage container create \
--name erpnext-backups \
--account-name $STORAGE_ACCOUNT
# Backup file shares using AzCopy
azcopy copy \
"https://$STORAGE_ACCOUNT.file.core.windows.net/erpnext-sites?$STORAGE_KEY" \
"https://$STORAGE_ACCOUNT.blob.core.windows.net/erpnext-backups/$(date +%Y%m%d)/" \
--recursive
3. Application Backup Job
# Create backup container instance
az container create \
--resource-group $RESOURCE_GROUP \
--name erpnext-backup \
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
--cpu 1 \
--memory 2 \
--restart-policy OnFailure \
--environment-variables \
DB_HOST=$DB_SERVER_NAME.postgres.database.azure.com \
DB_USER=$DB_ADMIN_USER \
STORAGE_ACCOUNT=$STORAGE_ACCOUNT \
--secure-environment-variables \
DB_PASSWORD=$DB_ADMIN_PASSWORD \
STORAGE_KEY=$STORAGE_KEY \
--azure-file-volume-account-name $STORAGE_ACCOUNT \
--azure-file-volume-account-key $STORAGE_KEY \
--azure-file-volume-share-name erpnext-sites \
--azure-file-volume-mount-path /home/frappe/frappe-bench/sites \
--command-line "/bin/bash -c 'bench --site frontend backup && az storage blob upload --account-name \$STORAGE_ACCOUNT --account-key \$STORAGE_KEY --container-name erpnext-backups --file /home/frappe/frappe-bench/sites/frontend/private/backups/*.sql.gz --name backup-$(date +%Y%m%d).sql.gz'" \
--subnet /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet
🔍 Troubleshooting
Container Health Issues
# Check container status
az container show \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--query containers[].instanceView.currentState
# Restart container group
az container restart \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend
# Execute commands in container
az container exec \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name backend \
--exec-command "/bin/bash"
Network Connectivity Issues
# Test database connectivity from container
az container exec \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name backend \
--exec-command "pg_isready -h $DB_SERVER_NAME.postgres.database.azure.com -U $DB_ADMIN_USER"
# Test Redis connectivity
az container exec \
--resource-group $RESOURCE_GROUP \
--name erpnext-backend \
--container-name backend \
--exec-command "redis-cli -h $REDIS_HOST -a $REDIS_KEY ping"
Storage Issues
# Check file share usage
az storage share show \
--name erpnext-sites \
--account-name $STORAGE_ACCOUNT \
--query "properties.quota"
# List files in share
az storage file list \
--share-name erpnext-sites \
--account-name $STORAGE_ACCOUNT \
--account-key $STORAGE_KEY
💰 Cost Optimization
1. Use Spot Instances (Preview)
# Deploy with spot instances for non-critical workloads
az container create \
--resource-group $RESOURCE_GROUP \
--name erpnext-worker-spot \
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
--cpu 1 \
--memory 2 \
--priority Spot \
--eviction-policy Delete
2. Optimize Container Sizes
# Monitor actual resource usage
az monitor metrics list \
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
--metric-names CpuUsage MemoryUsage \
--aggregation Average \
--interval PT1H
# Adjust container sizes based on usage
3. Schedule Scaling
# Use Azure Functions to scale down during off-hours
# Create timer-triggered function to stop/start containers
🎯 Production Considerations
- High Availability: Deploy multiple container groups across availability zones
- Disaster Recovery: Set up geo-replication for database and storage
- Security: Use managed identities instead of keys where possible
- Monitoring: Set up comprehensive dashboards in Azure Monitor
- Compliance: Enable Azure Policy for compliance enforcement
📋 Verification Checklist
# Check all container groups are running
az container list --resource-group $RESOURCE_GROUP --output table
# Verify application is accessible
curl -I http://$FRONTEND_FQDN:8080
# Check database connectivity
az postgres flexible-server show \
--resource-group $RESOURCE_GROUP \
--name $DB_SERVER_NAME \
--query state
# Verify Redis is accessible
az redis show \
--resource-group $RESOURCE_GROUP \
--name $REDIS_NAME \
--query provisioningState
# Check storage account
az storage account show \
--resource-group $RESOURCE_GROUP \
--name $STORAGE_ACCOUNT \
--query provisioningState
➡️ Next Steps
- Configure custom domain and SSL certificate
- Set up continuous deployment from Azure DevOps/GitHub
- Implement comprehensive monitoring and alerting
- Configure backup and disaster recovery procedures
- Review and implement production hardening (see
03-production-managed-setup.md)
⚠️ Important Notes:
- Container Instances have a maximum of 4 vCPUs and 16GB RAM per container
- For larger deployments, consider using AKS instead
- Monitor costs as Container Instances are billed per second
- Implement proper secret management using Key Vault
- Regular security updates for container images are essential