Securely Deploying a Python Web App with Private Storage on Azure Using Terraform
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
- Python Web App: Hosted on Azure App Service with a Linux-based Premium Plan for flexibility and scalability.
- Storage Account: Secure file storage in Azure using Blob containers, which is connected to the web app.
- Private Networking: A Virtual Network (VNet) and Private Endpoint to ensure resources can communicate securely within a private network.
- 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:
- Azure Account: An active Azure subscription with appropriate permissions.
- Terraform Installed: Version 1.5.0 or higher.
- Azure CLI Installed: For Azure authentication via the command line.
- 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:
- Initialize Terraform:
terraform init
2. Validate the Configuration:
terraform validate
terraform validate
3. Plan the Deployment:
terraform plan -out=tfplan
4. Apply the Deployment:
terraform apply tfplan
terraform apply
5. To Destroy
terraform 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.