Deploying Azure App Service with Terraform
Learn how to set up and manage Azure App Service for web applications using Terraform, including scaling, monitoring, and best practices
Deploying Azure App Service with Terraform
Azure App Service is a fully managed platform for building, deploying, and scaling web apps. This guide demonstrates how to set up and manage Azure App Service using Terraform.
Video Tutorial
Learn more about managing Azure App Service with Terraform in this comprehensive video tutorial:
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Resource group created
- Application code ready for deployment
Project Structure
terraform-azure-appservice/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── appservice/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── app/
└── src/
App Service Configuration
Create modules/appservice/main.tf
:
# App Service Plan
resource "azurerm_service_plan" "main" {
name = "${var.project_name}-plan"
location = var.location
resource_group_name = var.resource_group_name
os_type = "Linux"
sku_name = var.sku_name
tags = var.tags
}
# App Service
resource "azurerm_linux_web_app" "main" {
name = "${var.project_name}-app"
location = var.location
resource_group_name = var.resource_group_name
service_plan_id = azurerm_service_plan.main.id
site_config {
always_on = true
http2_enabled = true
application_stack {
node_version = "18-lts" # Or your preferred runtime
}
ip_restriction {
ip_address = var.allowed_ip_range
priority = 100
action = "Allow"
}
cors {
allowed_origins = var.cors_allowed_origins
}
}
app_settings = {
"WEBSITE_NODE_DEFAULT_VERSION" = "~18"
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
"DOCKER_REGISTRY_SERVER_URL" = var.docker_registry_url
"DOCKER_REGISTRY_SERVER_USERNAME" = var.docker_registry_username
"DOCKER_REGISTRY_SERVER_PASSWORD" = var.docker_registry_password
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.main.instrumentation_key
}
identity {
type = "SystemAssigned"
}
auth_settings {
enabled = true
default_provider = "AzureActiveDirectory"
unauthenticated_client_action = "RedirectToLoginPage"
active_directory {
client_id = var.aad_client_id
client_secret = var.aad_client_secret
allowed_audiences = [var.app_url]
}
}
logs {
http_logs {
file_system {
retention_in_days = 7
retention_in_mb = 35
}
}
application_logs {
file_system_level = "Information"
}
}
tags = var.tags
}
# Application Insights
resource "azurerm_application_insights" "main" {
name = "${var.project_name}-appinsights"
location = var.location
resource_group_name = var.resource_group_name
application_type = "web"
tags = var.tags
}
# Custom Domain
resource "azurerm_app_service_custom_hostname_binding" "main" {
hostname = var.custom_domain
app_service_name = azurerm_linux_web_app.main.name
resource_group_name = var.resource_group_name
depends_on = [
azurerm_dns_txt_record.verification
]
}
# SSL Certificate
resource "azurerm_app_service_certificate" "main" {
name = "${var.project_name}-cert"
resource_group_name = var.resource_group_name
location = var.location
pfx_blob = filebase64(var.ssl_certificate_path)
password = var.ssl_certificate_password
}
# SSL Binding
resource "azurerm_app_service_certificate_binding" "main" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.main.id
certificate_id = azurerm_app_service_certificate.main.id
ssl_state = "SniEnabled"
}
Scaling Configuration
- Auto-scaling Rules
resource "azurerm_monitor_autoscale_setting" "main" {
name = "${var.project_name}-autoscale"
resource_group_name = var.resource_group_name
location = var.location
target_resource_id = azurerm_service_plan.main.id
profile {
name = "defaultProfile"
capacity {
default = var.default_instance_count
minimum = var.min_instance_count
maximum = var.max_instance_count
}
rule {
metric_trigger {
metric_name = "CpuPercentage"
metric_resource_id = azurerm_service_plan.main.id
time_grain = "PT1M"
statistic = "Average"
time_window = "PT5M"
time_aggregation = "Average"
operator = "GreaterThan"
threshold = 75
}
scale_action {
direction = "Increase"
type = "ChangeCount"
value = "1"
cooldown = "PT5M"
}
}
rule {
metric_trigger {
metric_name = "CpuPercentage"
metric_resource_id = azurerm_service_plan.main.id
time_grain = "PT1M"
statistic = "Average"
time_window = "PT5M"
time_aggregation = "Average"
operator = "LessThan"
threshold = 25
}
scale_action {
direction = "Decrease"
type = "ChangeCount"
value = "1"
cooldown = "PT5M"
}
}
}
}
Network Configuration
- Virtual Network Integration
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
location = var.location
resource_group_name = var.resource_group_name
address_space = ["10.0.0.0/16"]
subnet {
name = "integration-subnet"
address_prefix = "10.0.1.0/24"
delegation {
name = "appservice"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
tags = var.tags
}
resource "azurerm_app_service_virtual_network_swift_connection" "main" {
app_service_id = azurerm_linux_web_app.main.id
subnet_id = azurerm_virtual_network.main.subnet.*.id[0]
}
Monitoring Configuration
- Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "app" {
name = "${var.project_name}-diag"
target_resource_id = azurerm_linux_web_app.main.id
log_analytics_workspace_id = var.log_analytics_workspace_id
log {
category = "AppServiceHTTPLogs"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
log {
category = "AppServiceConsoleLogs"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
metric {
category = "AllMetrics"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
}
resource "azurerm_monitor_metric_alert" "response_time" {
name = "${var.project_name}-response-time-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_linux_web_app.main.id]
description = "Alert when response time is high"
criteria {
metric_namespace = "Microsoft.Web/sites"
metric_name = "HttpResponseTime"
aggregation = "Average"
operator = "GreaterThan"
threshold = 5
}
action {
action_group_id = var.action_group_id
}
}
Deployment Slots
- Staging Slot
resource "azurerm_linux_web_app_slot" "staging" {
name = "staging"
app_service_id = azurerm_linux_web_app.main.id
site_config {
always_on = true
http2_enabled = true
application_stack {
node_version = "18-lts"
}
}
app_settings = merge(
azurerm_linux_web_app.main.app_settings,
{
"ASPNETCORE_ENVIRONMENT" = "Staging"
}
)
identity {
type = "SystemAssigned"
}
tags = var.tags
}
resource "azurerm_app_service_slot_virtual_network_swift_connection" "staging" {
slot_name = azurerm_linux_web_app_slot.staging.name
app_service_id = azurerm_linux_web_app.main.id
subnet_id = azurerm_virtual_network.main.subnet.*.id[0]
}
Best Practices
-
Security
- Enable authentication
- Use managed identities
- Implement IP restrictions
- Enable HTTPS only
-
Performance
- Enable auto-scaling
- Configure staging slots
- Use premium tier for production
- Enable HTTP/2
-
Monitoring
- Enable Application Insights
- Configure alerts
- Set up logging
- Monitor performance
-
Cost Optimization
- Right-size app service plan
- Use auto-scaling
- Monitor usage
- Consider reserved instances
Advanced Features
- WebJobs
resource "azurerm_app_service_webjob_continuous" "example" {
name = "example-webjob"
app_service_id = azurerm_linux_web_app.main.id
command = "node worker.js"
script_name = "worker.js"
script_content = filebase64("${path.module}/worker.js")
}
- Hybrid Connections
resource "azurerm_app_service_hybrid_connection" "example" {
app_service_name = azurerm_linux_web_app.main.name
resource_group_name = var.resource_group_name
relay_id = azurerm_relay_hybrid_connection.example.id
hostname = "internal.example.com"
port = 3306
}
Conclusion
You’ve learned how to set up and manage Azure App Service using Terraform. This setup provides:
- Secure web app deployment
- Auto-scaling capabilities
- Monitoring and alerts
- Custom domain and SSL
Remember to:
- Monitor performance
- Implement security best practices
- Optimize costs
- Use deployment slots for zero-downtime deployments