Early in my Terraform training, my attention was naturally focused on HashiCorp Configuration Language, or HCL: resources, variables, local values, data sources, outputs, state, providers, dependencies, and the familiar workflow:
terraform init
terraform validate
terraform plan
terraform apply
Those concepts still matter, but Terraform is becoming less mysterious. Once I find the appropriate resource in the Terraform Registry and review its arguments and exported attributes, the pattern is usually understandable. The Registry does not make design decisions for me, but it shows how a provider exposes a cloud service through code.
I already have a fundamentals-level understanding of Azure. The training is now pushing me beyond knowing what the services are and toward understanding how they are assembled into a functioning environment.
Moving from Platform Foundations to Workloads
My previous update covered the foundations of a small Azure platform:
- Remote Terraform state in Azure Storage
- Separate development and production configurations
- Resource groups and naming conventions
- Azure Key Vault
- Azure role-based access control
- Log Analytics workspaces
- Azure Monitor diagnostic settings
- Resource-provider registration
- Local runner scripts for repeatable Terraform execution
That stage showed me that Infrastructure as Code is more than a faster way to create resources. State, identity, permissions, logging, environment separation, and repeatable execution all require deliberate design.
The latest labs moved into networking and compute, where I created a virtual network, multiple subnets, calculated subnet ranges, a network security group, a public IP address, a network interface, an SSH key, and a Linux virtual machine. The course then extended that design into Azure Bastion, Key Vault-based SSH-key storage, managed identities, virtual-machine extensions, and Microsoft Entra ID authentication.
Networking Makes the Architecture Visible
Networking is where the environment began to feel tangible. Resource groups, storage accounts, Key Vault, and Log Analytics can each be understood on their own. Networking forces those pieces into a connected design.
A virtual machine needs a network interface. The interface needs an IP configuration tied to a subnet, which sits inside a virtual network with an address space. Security rules, routing, name resolution, and access methods all become part of the discussion.
Terraform expresses these relationships through references and dependencies, but it does not decide what the network should look like. I still need to answer questions such as:
- How large should the virtual network address space be?
- Which services require dedicated subnets?
- How much room should be reserved for growth?
- Which traffic should be allowed or denied?
- Does a workload need a public IP address?
- How should administrators reach private workloads?
Terraform can reliably implement the answers, but the answers must come from an architectural design.
CIDR Subnetting Became More Than a Math Exercise
One of the most useful exercises was CIDR subnetting. I defined a base address space as a variable and used local values with Terraform’s cidrsubnet function to calculate subnet ranges. A calculated design is more consistent than manually entered ranges and easier to follow when the surrounding network changes.
variable "base_address_space" {
type = string
}
locals {
workload_subnet_address = cidrsubnet(
var.base_address_space,
8,
1
)
bastion_subnet_address = cidrsubnet(
var.base_address_space,
10,
2
)
}
The real lesson was why address planning must happen before workloads and supporting services are deployed. When the training introduced Azure Bastion, the subnet design had to be revisited. A network may appear complete until a new service introduces another requirement.
From a Public IP to Azure Bastion
My first connection to the Linux virtual machine was direct: a public IP on the network interface, a generated SSH key, and an SSH session. That made the resource chain clear:
- The virtual network provides the larger network boundary.
- A subnet provides an address range for the workload.
- A network security group controls permitted traffic.
- A network interface connects the virtual machine to the subnet.
- The public IP provides an externally reachable address.
- The SSH key provides the authentication material.
The training then reorganized the network to accommodate Azure Bastion, which provides a managed connection path instead of treating public exposure as the default. Microsoft documents that Bastion can provide SSH connectivity to Linux virtual machines without exposing SSH ports to the public Internet, with connection options varying by configuration and SKU.
The question is no longer simply, “How do I connect to the VM?” It becomes: Should the VM have a public IP? Where does the administrative connection originate? Which connection method fits the requirement? How is access authorized?
Storing SSH Keys More Intentionally
The generated SSH key was initially stored locally. The next step moved it into Azure Key Vault, connecting this work to the security foundation from my previous training. Key Vault was no longer an isolated platform service; it became an active part of the VM access design.
That raises important questions about who can retrieve the key, how Key Vault access is granted, whether operations are logged, how the key will be rotated, and whether a shared SSH key should be used at all.
The technical task is storing the value in the correct Key Vault. The architectural task is deciding how administrative access is governed throughout its lifecycle.
Managed Identity Connects the Workload to Azure
The course then moved into system-assigned managed identities, which are tied to the lifecycle of the resource on which they are enabled. Given my background in identity and security, this is where the infrastructure became especially interesting: the virtual machine could now have its own Azure identity.
That identity can be granted access to other Azure resources without requiring credentials to be embedded in the configuration or placed on the server. Terraform makes enabling it concise, but the implications are broader:
- What does the workload need to access?
- At which scope should access be granted?
- Which Azure role provides the minimum necessary permissions?
- How will role assignments be reviewed and governed?
Enabling Microsoft Entra ID Authentication
Next came Microsoft Entra ID authentication for the Linux virtual machine, enabled through a VM extension deployed with Terraform. The resource itself is easy to follow once the provider documentation is identified; the important part is understanding the Azure capabilities being enabled.
Microsoft documents that Entra ID authentication for supported Linux virtual machines uses OpenSSH certificate-based authentication, can use Azure RBAC to distinguish users from administrators, and can incorporate Conditional Access. The virtual machine requires a system-assigned managed identity and the login extension.
Owner or Contributor permissions over a virtual machine do not automatically grant Microsoft Entra sign-in rights. Users require an appropriate login role, such as Virtual Machine User Login or Virtual Machine Administrator Login.
That separation distinguishes managing the Azure resource from administering the guest operating system, and regular sign-in from administrative sign-in. Terraform can deploy the extension and automate role assignments, but understanding these control planes is essential to configuring them correctly.
Reading the Terraform Registry as a Learning Tool
Translating an Azure requirement into Terraform is becoming a repeatable workflow:
- Identify the Azure service or architectural capability.
- Locate the relevant resource or data source in the Terraform Registry.
- Review the required and optional arguments and exported attributes.
- Identify dependencies on existing resources.
- Determine which values belong in variables or local values.
- Connect resources through references rather than duplicated values.
- Validate, review the plan, apply, and verify the Azure result.
- Revisit the design when the implementation reveals a missing dependency.
The Registry tells me what a resource is called, which arguments are required, which attributes are available, and how it is expressed in HCL. It does not tell me whether the resource should be deployed, where it belongs, who should have access, what should be logged, or how it will be operated. Those are architecture questions.
The Code Is Not the Architecture
Terraform is a way to express and implement an architecture. It is not a replacement for one.
A configuration can deploy successfully and still represent a weak design, with overly broad network access, excessive permissions, unmanaged secrets, insufficient monitoring, or a poorly planned address space. Conversely, a good design is difficult to maintain if it depends on undocumented manual steps.
| Terraform provides | Architecture must define |
|---|---|
| A declarative resource model | The services and boundaries that are required |
| Repeatable deployment | An appropriate target design |
| Variables and reusable expressions | Standards for environments and naming |
| Resource references and dependencies | The intended relationships between services |
| Plans showing proposed changes | The criteria used to evaluate those changes |
| State tracking | Appropriate state and lifecycle boundaries |
| Automated role assignments | A least-privilege access model |
| Repeatable network deployment | A viable addressing and segmentation strategy |
| VM extensions and identity configuration | An approved authentication and administration model |
Terraform can make a design repeatable. It cannot make a poorly considered design correct.
Learning Azure Beyond the Fundamentals
My Azure fundamentals certification gave me a useful vocabulary and conceptual map. The labs are helping me move from recognition to implementation.
I can now see how subnet planning affects future platform services, how public IP addresses affect exposure, how Bastion changes the administrative path, how Key Vault and managed identities participate in access design, and how RBAC separates resource management from operating-system login.
None of these services is new to me conceptually. What is new is implementing them together and seeing their dependencies in code, which is a meaningful step beyond Azure fundamentals.
An Important Lesson About Change
The labs also covered replacing resources when a change cannot be applied in place. A Terraform plan showing that an object will be updated, created, destroyed, or replaced is not merely a technical preview. It describes operational impact.
A replacement may be harmless in a disposable lab but unacceptable for a production workload with persistent data, a critical network dependency, or an established service endpoint.
That makes reviewing terraform plan one of the most important parts of the workflow. Before approving a change, I need to understand why Terraform is proposing it, whether an address or dependency will change, whether application data is involved, whether a maintenance window is needed, and whether the configuration can be redesigned to reduce disruption.
What Comes Next
My next section covers automating identity and access management in Microsoft Entra ID, including the AzureAD provider, groups, users, Entra ID data sources, and group membership.
That extends the training from Azure resource identity into directory identity and connects directly with the Microsoft 365, Entra ID, security, and governance work already familiar to me.
The Terraform patterns should remain recognizable. The more interesting questions will be about lifecycle and governance: Should Terraform create an identity or only reference it? Which system should be authoritative? What happens when a user leaves? How should existing directory objects be brought under management? Which assignments belong in code?
Those questions are larger than Terraform, and that is exactly why this training is becoming valuable.
Final Thoughts
Terraform is beginning to feel relatively straightforward. That does not mean every configuration is simple or that the Registry replaces experience. It means that once I identify the resource I need, I can usually follow its documentation and connect it to the rest of the configuration.
The more substantial challenge is knowing what I should build.
Each new resource teaches me a little more Terraform. More importantly, each relationship teaches me more about Azure architecture.
A successful terraform apply confirms that Azure accepted the configuration. It does not, by itself, confirm that the environment is secure, supportable, scalable, resilient, or operationally appropriate.
Related Reading
- From Learning HCL to Building an Azure Platform with Terraform
- Learning Terraform from the Language Up
- From Portals to Repeatable Infrastructure: Learning Infrastructure as Code with Terraform
- Building a Comfortable Training Setup in My Basement Home Lab

