Microsoft certification badges banner
Headshot of Michael Korting

Blog

Microsoft 365 • Security • Compliance

From Learning HCL to Building an Azure Platform with Terraform

After spending the first part of my Terraform training learning the language, I have moved into remote state, environment separation, Azure Key Vault, RBAC, Log Analytics, diagnostic settings, and the operational details that turn individual resources into a structured platform.

In my previous Terraform update, I compared two approaches to learning: deploying Azure resources immediately and building an understanding of the language first. My initial experience was results-first: follow a configuration, authenticate, run Terraform, and watch resources appear. My current course began with Infrastructure as Code principles, HCL, variables, outputs, validation, workspaces, and modules.

I have now completed 75 of the course’s 116 modules. The training has shifted from learning Terraform primarily as a language to using that language to assemble a more complete Azure design.

The first 50 modules helped me understand Terraform’s structure. The next 25 have connected those concepts to state, environments, naming, identity, access, and monitoring. I am moving beyond isolated deployments toward understanding how resources fit together.

Moving Beyond the First Deployment

Creating a resource group and running Terraform’s initialization, planning, and apply commands remains a useful starting point. A repeatable operating model raises larger questions: Where does state live? How are development and production separated? Which identity runs the configuration, what permissions does it receive, and where do logs go?

The latest training has begun answering those questions.

Building the Azure Foundation

Starting with an Azure subscription and resource group, I created a dedicated storage account, a blob container named tfstate, separate state files, and environment-specific backend values. State is Terraform’s record of the infrastructure it manages, not simply a byproduct of deployment.

A Terraform backend determines where state is stored. HashiCorp’s azurerm backend stores state as a blob within a container in an Azure Storage account. It also supports state locking and consistency checking through Azure Blob Storage capabilities.

Understanding the Bootstrap Problem

Terraform needs its Azure backend resources to exist before it can store state there. In a lab, those resources can be created directly before configuring the backend. A larger implementation needs a documented, controlled, repeatable bootstrap process. Infrastructure as Code does not remove dependencies; it makes their order more visible.

My current design now includes Azure Storage-based state for development and production, along with state files supporting the DevOps and observability portions of the environment.

Related resources do not necessarily need one shared state boundary. I am beginning to consider state in terms of responsibility and lifecycle rather than putting everything into a single configuration.

Remote State Changes the Conversation

A remote backend moves state away from being only a file on one workstation. HashiCorp explains that a backend defines where Terraform stores state data. A backend configuration can allow people and automation processes to work against a common state location rather than relying on separate local copies.

That introduces decisions about storage and state-file naming, authentication, access control, environment boundaries, deletion protection, and backend initialization. Remote storage improves the operating model, but security still extends to plan files, scripts, environment variables, logs, and source control. Marking a value sensitive does not protect every artifact that may contain it.

HashiCorp recommends using environment variables for credentials and other sensitive backend information. Its documentation warns that values supplied directly through backend configuration can be written into the .terraform directory and plan files.

Creating Repeatable Local Runner Scripts

Using Git Bash and shell scripts, I created local runners that supply Terraform variables and backend configuration. They reduce retyping while making the target environment, variable file, state file, and command sequence explicit.

A command entered correctly today can be mistyped tomorrow. A runner script makes the expected process reviewable alongside the configuration, especially when switching between development and production. This is still local execution, not a deployment pipeline, but it defines the inputs and sequence a future pipeline would require.

Development and Production Configurations

Development and production now follow the same foundational pattern while retaining distinct resources, inputs, and state. Variables, local values, outputs, and naming conventions have become practical tools for expressing those differences without letting duplicated configurations drift.

  • Terraform state
  • Resource groups
  • Storage accounts using a randomized naming pattern
  • tfstate blob containers
  • DevOps and observability state files
  • Observability resource groups
  • Log Analytics workspaces
  • DevOps resource groups
  • Azure Key Vaults
  • RBAC role assignments for the current user
  • Key Vault diagnostic settings connected to the appropriate Log Analytics workspace

Naming must communicate purpose and environment while satisfying Azure’s rules. Storage accounts and Key Vaults require globally unique names; random_string helps meet that requirement without replacing the broader naming convention.

Microsoft’s Terraform example for Azure Key Vault similarly demonstrates using random_string to help construct a Key Vault name.

Getting the Current Azure Identity

A Terraform data source retrieves the current deployment identity rather than requiring a hardcoded object ID. Other resources can reference that information, connecting identity discovery to access configuration.

In my lab, I use the current identity when creating an Azure role assignment for the Key Vault. Microsoft’s Terraform Key Vault example uses azurerm_client_config to retrieve information about the current principal and references its object ID in the configuration.

This turns an earlier language lesson into an operational relationship: a data source reads existing information, a resource uses its attributes, and Terraform understands the resulting dependency.

Adding Azure Key Vault and RBAC

Azure Key Vault establishes a security boundary around secrets, keys, and certificates. Deploying the service is only part of the task; its access model needs equal attention.

My configuration creates Key Vaults for the appropriate environments, obtains the current Azure identity, and creates RBAC role assignments for that identity.

The role assignment connects the deployment principal to the required scope, while resource references express the relationships. This is where my identity and security background intersects with Infrastructure as Code: repeatable access configuration still requires deliberate least-privilege decisions. Successful deployment is not automatically secure deployment.

Connecting Key Vault to Log Analytics

I then created Azure Monitor diagnostic settings to route Key Vault monitoring data to the appropriate Log Analytics workspace.

The diagnostic setting references both the Key Vault and workspace, connecting security resources to their monitoring destination. Each environment must send data to the correct workspace.

Microsoft documents that Key Vault logging can capture authenticated REST API requests, failed requests, Key Vault management operations, and operations involving keys and secrets. The logs can be sent to destinations that include a Log Analytics workspace.

Azure Monitor also provides metrics and logs that can be used to examine Key Vault requests, failures, latency, and availability.

My takeaway is that monitoring belongs in the resource design, not only in a later operational task. Defining diagnostics in Terraform makes observability part of the intended architecture rather than a manual follow-up.

When Azure Resource Providers Enter the Story

Deploying Log Analytics and diagnostic settings required the relevant Azure resource-provider namespaces to be registered. A resource definition can be valid while the subscription is not ready to deploy that resource type.

Failures can originate in syntax, inputs, provider configuration, authentication, authorization, naming, registration, dependencies, the backend, or service-side validation. Reading the error and identifying the failing layer matters as much as knowing the resource syntax. I am learning to interpret the interaction among Terraform, the AzureRM provider, Azure Resource Manager, and the target service instead of assuming every failure requires an HCL change.

The Architecture I Have Now

At this stage, the lab has developed into three related areas.

AreaImplemented componentsDesign purpose
Terraform StateResource groups, storage accounts, tfstate containers, environment-specific backend configuration, and separate DevOps and observability state files.Provide durable state storage and clearer lifecycle boundaries.
ObservabilityDedicated resource groups and Log Analytics workspaces for development and production.Give each environment an intentional monitoring destination.
DevOps and SecurityDedicated resource groups, Key Vaults, current-principal discovery, RBAC assignments, and diagnostic settings.Connect identity, access, secret-management infrastructure, and monitoring.

This remains a learning environment, but its components now form a small, connected platform.

What Has Changed in My Understanding

At 50 modules, I was learning how configurations are structured. At 75, I am seeing why their boundaries matter. State needs protection, environments need separation, execution needs consistent inputs, identities need scoped access, and monitoring needs a defined destination.

The central question has shifted from “Can I deploy this resource?” to “Can I recreate, explain, review, and operate this environment predictably?” Those considerations bring the exercises closer to implementation work.

What Comes Next

There are still 41 modules remaining in the course.

The next stage begins in Section 7, where the course moves into Azure networking, Linux virtual machines, SSH access, Azure Bastion, and VM identity. These upcoming modules will build on the state, Key Vault, and access foundations I have already created.

SequenceUpcoming topic
1Creating a Virtual Network and subnet
2Network Security Groups and public IP addresses
3Creating network interfaces and SSH keys
4Creating a Linux VM and connecting through local SSH
5Reorganizing subnets and creating an Azure Bastion subnet and host
6Storing SSH keys in Azure Key Vault
7Connecting to the VM through Azure Bastion
8Adding a system-assigned managed identity and enabling Microsoft Entra ID authentication through a VM extension

Final Thoughts

My previous update focused on learning Terraform from the language up. Now variables, local values, data sources, references, outputs, and backend configuration are working together in a design with remote state, separated environments, identity-aware access, and centralized monitoring.

A green terraform apply is satisfying, but it is not the final measure of good Infrastructure as Code. The environment also needs to be maintainable, secure, observable, understandable, and repeatable.

At approximately 65 percent through the course, I am learning more than how to create resources. I am learning how state, identity, security, and operations connect. The configurations are becoming more complex, but the larger design is becoming easier to understand.

Related Reading

References