Imagine: you deployed a VM via the Portal, but an hour later it's unreachable—you forgot to open a port in NSG. Or you chose B-series for production, and after 10 minutes the CPU credits ran out—the application crashed. Such mistakes cost time and money. We often encounter situations where disks are not encrypted, NSG allows all traffic, and the VM password is stored in a plain-text file. All of this is a direct path to data leaks or downtime. Proper configuration eliminates these risks and reduces costs by 15,000–25,000 RUB per month for a typical project through SKU optimization and VMSS usage. We have prepared a practical guide to setting up production-ready VMs. Our experience: over 5 years in Azure, 50+ deployed projects.
How to Choose the VM Size?
Azure offers dozens of series. For most tasks, the following table applies. The wrong choice is a common cause of overspending: B-series in production can cost 30% more due to downtime, and Premium SSD on a test environment is wasted money.
| Series | Purpose | Example | Recommendation |
|---|---|---|---|
| D-series (Dsv5) | General purpose, web, app servers | Standard_D4s_v5 | Standard for most projects |
| E-series | Memory-intensive: Redis, SAP, large DBs | Standard_E8s_v5 | For in-memory workloads |
| F-series | CPU-intensive: game servers, batch | Standard_F8s_v2 | For compute-heavy tasks |
| L-series | Storage-optimized: NoSQL, data warehouses | Standard_L8s_v3 | For disk I/O |
| B-series | Burstable: dev/test environments | Standard_B2s | Dev only. Not for production |
B-series for dev saves 60–70% compared to D-series. But for production, it's unsuitable: CPU credits exhaust under load.
Terraform Configuration
Here is an example production VM configuration with Terraform. Key points: only SSH keys, disk encryption, SystemAssigned identity, cloud-init.
resource "azurerm_linux_virtual_machine" "app" {
name = "vm-app-prod-01"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
size = "Standard_D4s_v5"
admin_username = "azureuser"
disable_password_authentication = true
# only SSH key
admin_ssh_key {
username = "azureuser"
public_key = data.azurerm_key_vault_secret.ssh_key.value
}
network_interface_ids = [azurerm_network_interface.app.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
disk_size_gb = 64
disk_encryption_set_id = azurerm_disk_encryption_set.main.id
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
identity {
type = "SystemAssigned"
}
custom_data = base64encode(file("cloud-init/app.yaml"))
boot_diagnostics {} # in managed storage, convenient for debugging
tags = {
environment = "production"
managed-by = "terraform"
}
}The parameter disable_password_authentication = true ensures passwords are not used. disk_encryption_set_id enables disk encryption with customer-managed keys via Key Vault.
Why Use Managed Identity?
Managed Identity provides VMs access to Azure services (Key Vault, Storage, ACR) without storing credentials on the disk. Unlike passwords or certificates, the identity is automatically rotated by the platform. Enable SystemAssigned—and the VM gets a service principal. Managed Identity completely eliminates storing secrets on the disk, which is 10 times more secure than passwords. According to official Microsoft documentation, this is the standard for enterprise projects.
# Assign Key Vault Secrets User role
PRINCIPAL_ID=$(az vm show \
--name vm-app-prod-01 \
--resource-group my-rg \
--query identity.principalId \
--output tsv)
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope /subscriptions/.../resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-kvOn the VM, the secret is read via IMDS without additional credentials:
# Get token via IMDS
TOKEN=$(curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
-H "Metadata: true" | jq -r '.access_token')
# Get secret
curl -s "https://my-kv.vault.azure.net/secrets/db-password?api-version=7.4" \
-H "Authorization: Bearer $TOKEN" | jq -r '.value'This approach eliminates storing secrets in code or repositories.
Security Hardening Checklist
| Measure | Description |
|---|---|
| SSH keys instead of passwords | disable_password_authentication = true |
| Managed Identity for service access | Instead of storing credentials on disk |
| Disk encryption | Use customer-managed keys via Key Vault |
| NSG: IP whitelist | Restrict access to necessary IPs and ports |
| Disable unnecessary ports | Remove RDP (3389) and other unused ports |
| Regular OS updates | Via cloud-init or update management |
Cloud-init for Initial Setup
Cloud-init is the de facto standard for initial VM configuration. It installs packages, starts services, and writes configs. We use a YAML file attached via custom_data. Learn more about the technology on Wikipedia.
Example cloud-init YAML
# cloud-init/app.yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
- nginx
- curl
- jq
runcmd:
- systemctl enable nginx
- systemctl start nginx
- curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh
# Install Azure Monitor Agent
- wget -qO- https://raw.githubusercontent.com/microsoft/OMS-Agent-for-Linux/master/installer/scripts/install_omsagent.sh | sh
write_files:
- path: /etc/nginx/sites-available/app
content: |
server {
listen 80;
location /health {
return 200 'ok';
}
location / {
proxy_pass http://127.0.0.1:8080;
}
}
This code installs nginx, monitoring, and a health-check endpoint.
What Does VMSS Give You?
Note: when you need multiple identical VMs with autoscaling, use Virtual Machine Scale Sets. They provide rolling updates, automatic recovery, and load balancer integration. Compare VMSS and individual VMs:
| Feature | Individual VMs | VMSS |
|---|---|---|
| Autoscaling | ❌ | ✅ |
| Rolling updates | ❌ | ✅ |
| Automatic recovery | ❌ | ✅ |
| LB integration | Manual | Automatic |
| Management cost | Higher | Up to 30% lower |
resource "azurerm_linux_virtual_machine_scale_set" "app" {
name = "vmss-app-prod"
resource_group_name = azurerm_resource_group.app.name
location = var.location
sku = "Standard_D4s_v5"
instances = 2
upgrade_mode = "Rolling"
rolling_upgrade_policy {
max_batch_instance_percent = 20
max_unhealthy_instance_percent = 20
max_unhealthy_upgraded_instance_percent = 5
pause_time_between_batches = "PT0S"
}
automatic_os_upgrade_policy {
enable_automatic_os_upgrade = true
disable_automatic_rollback = false
}
# ... rest of configuration similar to VM
}VMSS deploys dozens of VMs 3 times faster than manual creation. Order an audit of your infrastructure—we'll find growth points.
Our Work Process
- Audit current infrastructure (if any) – analyze existing VMs, load, costs. Collect metrics for 1 week for correct right-sizing.
- Design – select SKUs, networks, security policies. We use Terraform for 99.9% of infrastructure described.
- Deployment via Terraform – all resources are code-versioned in Git.
- Monitoring setup – Azure Monitor, Log Analytics, alerts for CPU, memory, disk (up to 10,000 IOPS).
- Application deployment – via cloud-init or CI/CD (Azure DevOps).
- Testing and handover – we test fault tolerance, collect metrics, perform load testing.
What's Included in the Result
- Terraform code (modular, commented, in your Git repository)
- Cloud-init scripts for applications
- Managed Identity for service access
- Monitoring and alerts for CPU, memory, disk, health
- Documentation for support and common operations
- Team training (1 hour, basics)
- Configuration warranty – 2 weeks after delivery: if something breaks, we fix it for free.
Timeline and Cost
Timeline: from 2 to 7 days depending on complexity (number of VMs, VMSS, integrations). Cost is calculated individually after audit. Example: deploying 3 production VMs with Terraform, monitoring, and CI/CD – 3-4 days. Average infrastructure savings can reach 15,000–30,000 RUB per month due to correct SKU selection and VMSS usage.
Contact us to discuss your task. Our experience: over 5 years and 50+ successful Azure projects. Get a consultation with no obligation.







