Terraform Provisioning Of Azure Storage Accounts And Remote State Backend Configuration

Almost every Azure project needs at least one storage account, and Terraform makes it easy to define them consistently with the right security and networking settings from day one. This reality drives the adoption of infrastructure as code for storage because manual portal clicks do not scale across environments and teams. Terraform provides a declarative surface where storage account topology, replication, encryption, and network posture are captured in versioned configuration files. The practice of defining storage accounts in Terraform with all the production-ready configuration options including replication, networking rules, lifecycle management, and encryption ensures that drift is reduced and that security baselines are enforced from initial creation rather than remediated later.

The significance of this approach extends beyond a single resource. Storage accounts are the data plane foundation for blobs, files, queues, and tables and for Terraform remote state. When storage accounts are created with Terraform, the same configuration frontend that provisions compute, networking, and identity can also manage the storage that holds Terraform state itself. This creates a closed loop where infrastructure is both defined and stored using the same tooling.

Provider And Version Constraints For Terraform AzureRM

The provider block establishes the contract between Terraform and Azure. The version constraint pins the toolchain to a known stable surface.

hcl terraform { required_version = ">= 1.5.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } } }

The required_version = ">= 1.5.0" ensures that language features and provider interaction behaviors are available. The impact of this constraint is that teams avoid unexpected breaking changes from older Terraform cores and benefit from consistent plan and apply semantics across contributors. The contextual layer connects this to CI pipelines where Terraform is executed in automation; a pinned version prevents pipeline failures caused by core upgrades.

hcl provider "azurerm" { features {} subscription_id = var.subscription_id }

The features {} block opts into default provider behaviors. The subscriptionid = var.subscriptionid binds the provider to a specific Azure subscription via variable input. This design allows the same configuration to target multiple subscriptions by changing a variable without modifying code. In practice this supports environment separation such as dev, test, and production.

The provider configuration directly influences authentication and API targeting. If the subscription_id is misconfigured, all resource addresses resolve to the wrong subscription and applies fail with authorization errors. The contextual layer ties this to state management because the backend also requires subscription context.

Resource Group Declaration As Foundation

A resource group provides the logical container for the storage account.

hcl resource "azurerm_resource_group" "storage" { name = "rg-storage-production" location = "East US" }

The name = "rg-storage-production" creates a deterministic naming pattern that is human readable and searchable in Azure. The location = "East US" sets the physical region for the group and for dependent resources that inherit location. The impact is that cost, latency, and compliance boundaries are established at creation time. East US is a common choice for US East workloads due to service availability.

The resource group is referenced later by name via azurermresourcegroup.storage.name and azurermresourcegroup.storage.location. This reference creates an implicit dependency so Terraform will create the group before the storage account. The contextual layer shows how resource groups act as scoping anchors for tags, policies, and access controls.

Basic Storage Account Definition With Production Hardening

The core storage account resource captures account kind, tier, replication, and security posture.

hcl resource "azurerm_storage_account" "main" { name = var.storage_account_name resource_group_name = azurerm_resource_group.storage.name location = azurerm_resource_group.storage.location account_tier = "Standard" account_replication_type = "GRS" account_kind = "StorageV2" https_traffic_only_enabled = true min_tls_version = "TLS1_2" infrastructure_encryption_enabled = true access_tier = "Hot" }

The name is supplied via var.storageaccountname to enforce naming rules centrally. The accounttier = "Standard" selects the general purpose performance class. The accountreplication_type = "GRS" enables Geo-redundant storage which replicates data synchronously within the primary region and asynchronously to a secondary region. The impact is higher durability for critical data and reduced risk of regional outage. The contextual layer notes that GRS increases cost versus LRS and requires recovery planning for failover.

The accountkind = "StorageV2" provides general purpose v2 capabilities including hierarchical namespace option and latest blob features. The httpstrafficonlyenabled = true forces all connections to use HTTPS and blocks HTTP. This reduces exposure to protocol downgrade attacks.

The mintlsversion = "TLS12" blocks older TLS versions. The infrastructureencryptionenabled = true enables double encryption where Microsoft-managed keys encrypt the storage service infrastructure. The accesstier = "Hot" optimizes for frequent access patterns.

Attribute Value Effect
account_tier Standard Cost effective general purpose tier
accountreplicationtype GRS Geo-redundant with async secondary
account_kind StorageV2 General purpose v2
httpstrafficonly_enabled true HTTP blocked
mintlsversion TLS1_2 Weak TLS blocked
infrastructureencryptionenabled true Double encryption active
access_tier Hot Optimized for frequent access

The configuration also allows public blob access to be controlled at the account. In a production deployment it is recommended to restrict access using a storage firewall, service endpoint, or private endpoint. The reference facts note that public network access is allowed in the example and must be hardened.

Customer Managed Key Encryption With Key Vault Integration

Customer-managed keys provide control over encryption keys.

hcl data "azurerm_client_config" "current" {}

The data source captures the current tenant and object ID for policy assignment.

hcl resource "azurerm_key_vault_access_policy" "client" { key_vault_id = azurerm_key_vault.storage.id tenant_id = data.azurerm_client_config.current.tenant_id object_id = data.azurerm_client_config.current.object_id key_permissions = [ "Create", "Delete", "Decrypt", "Encrypt", "Get", "GetRotationPolicy", "List", "Purge", "Recover", "Restore", "SetRotationPolicy", "Sign", "Update", "UnwrapKey", "Verify", "WrapKey" ] }

This policy allows Terraform to create and manage the encryption key. The impact is that the client principal can perform full lifecycle operations on the key. The contextual layer ties this to least privilege reviews because broad permissions should be narrowed in production.

hcl resource "azurerm_key_vault_access_policy" "storage" { key_vault_id = azurerm_key_vault.storage.id tenant_id = data.azurerm_client_config.current.tenant_id object_id = azurerm_storage_account.main.identity[0].principal_id key_permissions = [ "Get", "WrapKey", "UnwrapKey" ] }

This policy allows the storage account identity to use the key. The storage account uses its managed identity principal_id to request key operations. The impact is that key usage is auditable and can be revoked by removing the policy.

hcl resource "azurerm_key_vault_key" "storage" { name = "storage-encryption-key" key_vault_id = azurerm_key_vault.storage.id key_type = "RSA" key_size = 2048 key_opts = ["decrypt", "encrypt", "wrapKey", "unwrapKey"] depends_on = [ azurerm_key_vault_access_policy.client ] }

The key is RSA with 2048-bit size. The depends_on ensures the access policy exists before key creation. The impact is that creation failures due to missing permissions are avoided.

hcl resource "azurerm_storage_account_customer_managed_key" "main" { storage_account_id = azurerm_storage_account.main.id key_vault_key_id = azurerm_key_vault_key.storage.id depends_on = [ azurerm_key_vault_access_policy.storage ] }

Applying customer-managed key to the storage account binds encryption to the Key Vault key. The depends_on ensures the storage access policy is in place first.

State File Backend Configuration For Azure Storage

Terraform state can be stored in Azure Storage. To configure the backend state you need the following Azure storage information:

  • storageaccountname: The name of the Azure Storage account.
  • container_name: The name of the blob container.
  • key: The name of the state store file to be created.
  • access_key: The storage access key.

Each of these values can be specified in the Terraform configuration file or on the command line. We recommend that you use an environment variable for the access_key value.

The backend requires elevated permissions on the storage account. Terraform retains this method for backwards compatibility, we do not recommend it for new workloads.

The following configuration options are always required for this method:

  • tenantid
    The tenant ID of the Microsoft Entra ID principal is required to authenticate to the storage account management and data plane. If using Azure CLI, this can be inferred from the CLI session. This can also be set via the ARM
    TENANT_ID environment variable.
  • subscriptionid
    The subscription ID of the storage account is required to query the management plane. If using Azure CLI, this can be inferred from the CLI session. This can also be set via the ARM
    SUBSCRIPTION_ID environment variable.
  • resourcegroupname
    The resource group name of the storage account is required to query the management plane.
  • storageaccountname
    The name of the storage account to write the state file blob to.
  • container_name
    The name of the storage account container to write the state file blob to.
  • key
    The name of the blob within the storage account container to write the state file to.

These optional configuration options apply when looking up the data plane URI from the management plane. They are not required when the data plane URI can be inferred from storageaccountname and container_name.

  • lookupblobendpoint
    Set to true to lookup the storage account data plane URI from the management plane.

Terraform state is stored in plain text and may contain secrets. If the state is incorrectly secured, unauthorized access to systems and data loss can result. In this example, Terraform authenticates to the Azure storage account using an Access Key. In a production deployment, it's recommended to evaluate the available authentication options supported by the azurerm backend and to use the most secure option for your use case.

The impact of storing state in Azure Storage is centralized locking and collaborative editing. The contextual layer connects this to the storage account security posture defined earlier: the same account that holds state must be hardened with private endpoints and firewall rules to avoid exposing secrets.

Operational Workflow Verification And Teardown

Creation is confirmed interactively.

  • Type yes and press Enter to confirm and create the Azure Storage Account and Resource Group.

After apply completes, verification is performed.

bash terraform show

This command will display the current state of the resources managed by Terraform.

After creation it is necessary to remove the unwanted resource to avoid extra cost from the side of azure.

bash terraform destroy

The destroy command removes all resources managed by the configuration. The impact is cost avoidance and environment cleanup. The contextual layer links this to state file consistency; destroying resources without updating state leads to orphaned entries.

Advantages And Disadvantages Of Terraform For Storage Account Management

Advantages of Create Azure Storage Account using Terraform:

  • Infrastructure as Code: Your Azure Storage Account can now be represented in the Terraform configuration files that can be handled by using version control, shared, are replicated across many different environments. This aids organization in maintaining best practices, consistency, reproducibility and collaboration within their group.
  • Automation and Repeatability: When you Terraform and configure automation, you can easily provision and manage Azure Storage Accounts, without any need of human interventions and consequently mistakes
  • Declarative Configuration: Terraform is declarative in nature, where you define the desired state of your cloud system including the related Azure Storage Account together with its configuration. Therefore, Terraform deals with the necessary API calls and procedures for creating or modifying resources at a lower level. Consequently, you can formulate the desired end state instead of diving into the details of every step which is necessary for the implementation.
  • Dependency Management: With terraform, it only takes one command and a bit of code to get all the Azure resources you need such as Storage Account, Resource Groups or Virtual Networks, and you can be sure that the dependencies between the resources are handled correctly, avoiding resource conflicts or errors.
  • Multi-Cloud and Cross-Platform Support: Terraform provides a choice to users that want to develop their infrastructure by using all of the supported cloud providers, including Azure, AWS, GCP, and others
  • This provision could be kept repetitive thereby allowing you to manage the identical storage accounts not just in different types of environments but in different deployments too.

Disadvantages of Create Azure Storage Account using Terraform:

  • Learning Curve: Terrraform uses its own domain-specific language HashiCorp Configuration Language or HCL and workflow and thus it can be challenging to master them for the first time provided that you may not be familiar with IaC tools. It is probable that grasp of concepts, syntax and best practices of Terraform may take some time and a lot of work.
  • State File Management: terraform employs a state file in order to maintain the status of your infrastructure resources. Proper management and storing of state files should be paid enough attention, because any inconsistency or loss of the state file will cause trouble in the management or updating of resources. A well-structured state file management system is an absolute necessity, particularly for groups or multi-member work.
  • Vendor Lock-in: While Terraform has the flexibility to support cloud services offered by different providers, its configuration files and modules can be classified as provider/version specific

The impact of advantages is reduced human error and faster onboarding of new environments. The impact of disadvantages is training cost and operational risk around state. The contextual layer shows that state file management and backend security must be addressed together with storage account hardening.

Declarative Configuration And Dependency Handling

This creates a cloud-based storage management, giving you a control of your storage accounts in any part of the, consequently, enabling you to manage your entire infrastructure using one common programming frontend.

The declarative nature means the user specifies desired end state and Terraform computes the diff. Dependency management ensures resource groups exist before storage accounts and access policies exist before keys. This reduces race conditions and API errors.

The combination of provider version pinning, resource group anchoring, storage account hardening, customer-managed keys, and remote state backend creates a production-ready pattern for Azure storage account provisioning with Terraform. The pattern balances security, repeatability, and operational control while acknowledging the learning curve and state management responsibilities inherent to infrastructure as code.

Sources

  1. Source Name
  2. Source Name
  3. Source Name
  4. Source Name

Related Posts