Skip to main content

Terraform Azure App Services

Introduction

Azure App Service is a fully managed platform for building, deploying, and scaling web applications. It supports multiple programming languages and frameworks, making it a versatile choice for hosting web applications, REST APIs, and mobile back ends. When combined with Terraform, you can automate the provisioning and management of these services, ensuring consistent deployment across different environments.

In this tutorial, we'll explore how to use Terraform to create and configure Azure App Services. By the end of this guide, you'll understand how to declare App Service resources in Terraform, configure application settings, and implement best practices for production deployments.

Prerequisites

Before getting started, ensure you have:

  • Terraform installed (version 1.0.0 or later)
  • Azure CLI installed and configured
  • An active Azure subscription
  • Basic understanding of Terraform concepts

Setting Up Your Terraform Project

Let's start by creating a new directory for our Terraform project and initializing it:

bash
mkdir terraform-azure-app-service
cd terraform-azure-app-service
touch main.tf variables.tf outputs.tf

Now, let's define the Azure provider in main.tf:

hcl
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}

provider "azurerm" {
features {}
}

Creating Basic Resources

Before creating an App Service, we need a resource group and an App Service Plan:

hcl
# Resource Group
resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.location
}

# App Service Plan
resource "azurerm_service_plan" "example" {
name = var.app_service_plan_name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
os_type = "Linux"
sku_name = "B1" # Basic tier, adjust as needed
}

Next, let's define the variables in variables.tf:

hcl
variable "resource_group_name" {
description = "Name of the resource group"
type = string
default = "rg-terraform-app-service"
}

variable "location" {
description = "Azure region to deploy resources"
type = string
default = "East US"
}

variable "app_service_plan_name" {
description = "Name of the App Service Plan"
type = string
default = "asp-terraform-example"
}

variable "app_name" {
description = "Name of the App Service"
type = string
default = "app-terraform-example"
}

Creating an Azure App Service

Now, let's create a basic App Service resource in main.tf:

hcl
# App Service
resource "azurerm_linux_web_app" "example" {
name = var.app_name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
}
}

This creates a basic Node.js app service. You can choose different application stacks like PHP, .NET, Python, etc., by modifying the application_stack block.

Configuring Application Settings

App Service applications often require environment-specific configuration. You can set these using application settings:

hcl
# App Service with application settings
resource "azurerm_linux_web_app" "example_with_settings" {
name = "${var.app_name}-with-settings"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
}

app_settings = {
"WEBSITE_NODE_DEFAULT_VERSION" = "~16"
"ENVIRONMENT" = "Production"
"DATABASE_URL" = "postgresql://user:password@localhost/dbname"
"API_KEY" = "your-api-key"
}
}
caution

Never hard-code sensitive values like database passwords or API keys in your Terraform files. Instead, use Terraform variables, environment variables, or a secret management solution like Azure Key Vault.

Using Connection Strings

For database connections, it's better to use the connection strings configuration:

hcl
resource "azurerm_linux_web_app" "example_with_connection_strings" {
name = "${var.app_name}-with-connections"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
}

connection_string {
name = "Database"
type = "SQLAzure"
value = "Server=tcp:${azurerm_mssql_server.example.fully_qualified_domain_name},1433;Database=${azurerm_mssql_database.example.name};User ID=${var.sql_admin_login};Password=${var.sql_admin_password};Encrypt=true;Connection Timeout=30;"
}
}

Setting Up Deployment Options

Azure App Service supports various deployment options. Here's how to configure deployment from a GitHub repository:

hcl
resource "azurerm_linux_web_app" "example_with_deployment" {
name = "${var.app_name}-with-deployment"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
}

source_control {
repo_url = "https://github.com/username/repo"
branch = "main"
manual_integration = true
use_mercurial = false
}
}

Configuring Custom Domains and SSL

For production applications, you'll want to configure custom domains and SSL certificates:

hcl
# Custom domain binding
resource "azurerm_app_service_custom_hostname_binding" "example" {
hostname = "www.example.com"
app_service_name = azurerm_linux_web_app.example.name
resource_group_name = azurerm_resource_group.example.name
}

# Managed Certificate (preview)
resource "azurerm_app_service_managed_certificate" "example" {
custom_hostname_binding_id = azurerm_app_service_custom_hostname_binding.example.id
}

# Certificate binding
resource "azurerm_app_service_certificate_binding" "example" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.example.id
certificate_id = azurerm_app_service_managed_certificate.example.id
ssl_state = "SniEnabled"
}

Scaling and Performance Optimization

You can configure auto-scaling for your App Service to handle varying loads:

hcl
# App Service Plan with scaling capabilities
resource "azurerm_service_plan" "scaling_example" {
name = "${var.app_service_plan_name}-scaling"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
os_type = "Linux"
sku_name = "P1v2" # Premium v2 tier for auto-scaling
}

# Auto-scale settings
resource "azurerm_monitor_autoscale_setting" "example" {
name = "autoscale-config"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
target_resource_id = azurerm_service_plan.scaling_example.id

profile {
name = "Default"

capacity {
default = 1
minimum = 1
maximum = 5
}

rule {
metric_trigger {
metric_name = "CpuPercentage"
metric_resource_id = azurerm_service_plan.scaling_example.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.scaling_example.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"
}
}
}
}

Deployment Slots for Zero-Downtime Deployments

Deployment slots allow you to deploy new versions of your application without downtime:

hcl
# Production App Service
resource "azurerm_linux_web_app" "production" {
name = var.app_name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
}
}

# Staging Deployment Slot
resource "azurerm_linux_web_app_slot" "staging" {
name = "staging"
app_service_id = azurerm_linux_web_app.production.id

site_config {
application_stack {
node_version = "16-lts"
}
}

app_settings = {
"ENVIRONMENT" = "Staging"
}
}

Creating Outputs

Let's define some useful outputs in outputs.tf:

hcl
output "app_service_url" {
value = "https://${azurerm_linux_web_app.example.default_hostname}"
}

output "app_service_name" {
value = azurerm_linux_web_app.example.name
}

output "resource_group_name" {
value = azurerm_resource_group.example.name
}

Architecture Diagram

Here's a visual representation of what we're building:

Complete Example

Here's a complete example combining all the concepts we've covered:

hcl
# main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}

provider "azurerm" {
features {}
}

# Resource Group
resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.location
}

# App Service Plan
resource "azurerm_service_plan" "example" {
name = var.app_service_plan_name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
os_type = "Linux"
sku_name = "P1v2"
}

# Production App Service
resource "azurerm_linux_web_app" "production" {
name = var.app_name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id

site_config {
application_stack {
node_version = "16-lts"
}
always_on = true
ftps_state = "Disabled"
health_check_path = "/health"
}

app_settings = {
"WEBSITE_NODE_DEFAULT_VERSION" = "~16"
"ENVIRONMENT" = "Production"
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.example.instrumentation_key
}

identity {
type = "SystemAssigned"
}

logs {
detailed_error_messages = true
application_logs {
file_system_level = "Information"
}
http_logs {
file_system {
retention_in_days = 7
retention_in_mb = 35
}
}
}
}

# Staging Deployment Slot
resource "azurerm_linux_web_app_slot" "staging" {
name = "staging"
app_service_id = azurerm_linux_web_app.production.id

site_config {
application_stack {
node_version = "16-lts"
}
always_on = true
}

app_settings = {
"ENVIRONMENT" = "Staging"
}
}

# Application Insights for monitoring
resource "azurerm_application_insights" "example" {
name = "${var.app_name}-insights"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
application_type = "web"
}

# Auto-scale settings
resource "azurerm_monitor_autoscale_setting" "example" {
name = "autoscale-config"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
target_resource_id = azurerm_service_plan.example.id

profile {
name = "Default"

capacity {
default = 1
minimum = 1
maximum = 5
}

rule {
metric_trigger {
metric_name = "CpuPercentage"


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)