Securely Deploying a Python Web App with Private Storage on Azure Using Terraform

Tanvir Ahmed
5 min readJan 20, 2025

--

Azure App Service with Private Storage

Introduction

In today’s cloud-centric world, deploying scalable, reliable, and secure applications is critical for businesses. Azure provides a comprehensive set of tools to help manage infrastructure securely, but manually provisioning these resources can often be tedious and error-prone. This is where Terraform, a powerful Infrastructure as Code (IaC) tool, comes into play.

In this blog post, we’ll walk through the steps to deploy a Python-based web application on Azure using Terraform. The deployment setup will include an App Service, a Storage Account for file storage, and a Private Endpoint to ensure secure communication between resources.

By the end of this guide, you’ll have a reusable Terraform configuration to deploy your own applications securely in Azure.

Key Features of This Deployment

  1. Python Web App: Hosted on Azure App Service with a Linux-based Premium Plan for flexibility and scalability.
  2. Storage Account: Secure file storage in Azure using Blob containers, which is connected to the web app.
  3. Private Networking: A Virtual Network (VNet) and Private Endpoint to ensure resources can communicate securely within a private network.
  4. Terraform Automation: Using Terraform to provision all resources in a modular, scalable manner following best practices.

GitHub Repository: You can access the full source code and additional configuration details at the following GitHub Repository

Prerequisites

Before you start, ensure that you have the following:

  1. Azure Account: An active Azure subscription with appropriate permissions.
  2. Terraform Installed: Version 1.5.0 or higher.
  3. Azure CLI Installed: For Azure authentication via the command line.
  4. Text Editor: Visual Studio Code or any text editor of your choice.

Project Structure

Here’s how we’ll structure our Terraform project to maintain readability and scalability:

Azure-WebApp-StorageAccount-with-Terraform/
├── main.tf # Main configuration for resources
├── variables.tf # Input variables for resource customization
├── outputs.tf # Outputs for key resource details
├── providers.tf # Azure provider and backend configuration
├── versions.tf # Terraform and provider version requirements
└── terraform.tfvars # Variable values for the environment

Step 1: Define Versions and Providers

We begin by specifying the required Terraform and Azure provider versions. Create the versions.tf file as follows:

terraform {
required_version = ">= 1.5.0"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.73"
}
}
}

Step 2: Declare Variables

Define the input variables to make the configuration more reusable and flexible. Add the following content to variables.tf:

variable "project_name" {
description = "The name of the project or application"
type = string
default = "sample"
}

variable "environment" {
description = "The environment type (e.g., DEV, PRD)"
type = string
default = "DEV"
}

variable "azure_region" {
description = "Azure region for deployment"
type = string
default = "japaneast"
}

variable "vnet_address_space" {
description = "VNet address space (CIDR)"
type = string
default = "10.200.0.0/16"
}

variable "subnet_address_prefix" {
description = "Subnet address prefix (CIDR)"
type = string
default = "10.200.1.0/24"
}

variable "app_service_plan_sku" {
description = "App Service Plan SKU"
type = string
default = "P1v2"
}

variable "storage_account_sku" {
description = "Storage Account SKU"
type = string
default = "Standard_LRS"
}

Step 3: Configure Resource Deployment

In main.tf, define the Azure resources.

Resource Group and Networking

resource "azurerm_resource_group" "main" {
name = "${var.project_name}-${var.environment}-rg"
location = var.azure_region
}

resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-${var.environment}-vnet"
address_space = [var.vnet_address_space]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_subnet" "main" {
name = "${var.project_name}-${var.environment}-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.subnet_address_prefix]
}

App Service Plan and Web App

resource "azurerm_service_plan" "main" {
name = "asp-${var.project_name}-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Linux"
sku_name = var.app_service_plan_sku
}

resource "azurerm_linux_web_app" "main" {
name = "webapp-${var.project_name}-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
service_plan_id = azurerm_service_plan.main.id

site_config {}
}

Storage Account and Blob Container

resource "azurerm_storage_account" "main" {
name = "st${lower(var.project_name)}${lower(var.environment)}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
}

resource "azurerm_storage_container" "main" {
name = "files"
storage_account_name = azurerm_storage_account.main.name
container_access_type = "private"
}

Private Endpoint and DNS

resource "azurerm_private_endpoint" "storage" {
name = "pe-${var.project_name}-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
subnet_id = azurerm_subnet.main.id

private_service_connection {
name = "storage-connection"
private_connection_resource_id = azurerm_storage_account.main.id
subresource_names = ["blob"]
is_manual_connection = false
}
}

resource "azurerm_private_dns_zone" "blob" {
name = "privatelink.blob.core.windows.net"
resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "main" {
name = "dns-link-${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.main.name
private_dns_zone_name = azurerm_private_dns_zone.blob.name
virtual_network_id = azurerm_virtual_network.main.id
}

Step 4: Define Outputs

Add the following outputs to outputs.tf to display key information after deployment:

output "web_app_url" {
value = azurerm_linux_web_app.main.default_hostname
}

output "storage_account_name" {
value = azurerm_storage_account.main.name
}

output "private_endpoint_ip" {
value = azurerm_private_endpoint.storage.private_service_connection[0].private_ip_address
}

Step 5: Deploy the Configuration

To deploy the resources, follow these steps:

  1. Initialize Terraform:

terraform init

Initialize Terraform

2. Validate the Configuration:

terraform validate

terraform validate

3. Plan the Deployment:

terraform plan -out=tfplan

Plan the Deployment

4. Apply the Deployment:

terraform apply tfplan

terraform apply

5. To Destroy

terraform destroy

To Destroy

Conclusion

In this blog, we showed how to deploy a secure Python-based web application on Azure using Terraform. This setup includes private endpoints, DNS integration, and best practices to ensure secure communication between resources.

By following these steps, you can automate the deployment of your own applications in Azure using Terraform. Customizing the configuration to meet your needs can help streamline your infrastructure management.

--

--

Tanvir Ahmed
Tanvir Ahmed

Written by Tanvir Ahmed

Tanvir is a Cloud & DevOps Engineer, AWS Certified. He focuses on multi-cloud, MLOps, and Data & AI, teaches others, and is always learning while enjoying life.

No responses yet