Lab Goal
This lab creates a single Azure Resource Group named
rg-tf-learning-001. Resource groups are management
containers in Azure, so this was a safe first target for learning
Terraform without creating compute, networking, storage, or database
resources.
The workflow covered:
- Creating a Terraform project folder in VS Code
- Authenticating to Azure with Azure CLI
- Writing a minimal Terraform configuration
- Initializing the Terraform working directory
- Formatting and validating the configuration
- Generating and reviewing a Terraform plan
- Applying the configuration to Azure
- Confirming Terraform state tracking
- Updating tags through Terraform
- Detecting and correcting manual portal drift
Tools Used
The lab used a modern local workflow instead of portal-only administration:
PowerShell 7
Azure CLI
Git
Terraform
Visual Studio Code
I verified the local tooling with:
terraform -version
az version
git --version
$PSVersionTable.PSVersion Project Folder
I created a dedicated folder for the lab and opened it in VS Code:
mkdir C:\Code\terraform-azure-lab
cd C:\Code\terraform-azure-lab
code .
The initial project structure was:
terraform-azure-lab
main.tf
variables.tf
outputs.tf
.gitignore
README.md Azure CLI Login
I authenticated to the correct Azure tenant using Azure CLI:
az login --tenant "<tenant-id>" --use-device-code
Then I confirmed the active subscription:
az account show -o table
For the local Terraform session, I set the Azure subscription and tenant environment variables:
$env:ARM_SUBSCRIPTION_ID = "<subscription-id>"
$env:ARM_TENANT_ID = "<tenant-id>"
These values should not be committed to Git or published publicly.
Git Ignore
I added a .gitignore file so local Terraform files and
state would not be committed:
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
For a local learning lab, local state is fine. In a real production workflow, Terraform state should be stored remotely with locking and appropriate access controls.
First Terraform Configuration
The first main.tf created only one Azure Resource Group:
terraform {
required_version = ">= 1.6.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
resource_provider_registrations = "none"
features {}
}
resource "azurerm_resource_group" "lab" {
name = "rg-tf-learning-001"
location = "eastus"
tags = {
environment = "lab"
owner = "me"
managed_by = "terraform"
}
}
The resource_provider_registrations = "none" setting kept
the lab focused. During the first plan, the AzureRM provider attempted
to register many Azure service namespaces that were not needed for a
simple resource group lab.
Initialize Terraform
The first Terraform command was:
terraform init
This initialized the working directory and downloaded the AzureRM provider. A successful initialization returns:
Terraform has been successfully initialized!
Format and Validate
Next, I formatted and validated the Terraform configuration:
terraform fmt
terraform validate
The expected validation result was:
Success! The configuration is valid.
Validation confirms that Terraform understands the configuration. It does not mean Azure has created anything yet.
Terraform Plan
Next, I generated a plan:
terraform plan
The important result was:
Plan: 1 to add, 0 to change, 0 to destroy.
Terraform showed that it would create only one resource:
azurerm_resource_group.lab
This is one of the most important Terraform habits: always read the plan before applying changes.
Terraform Apply
After confirming the plan, I applied the configuration:
terraform apply
Terraform displayed the plan again and prompted for confirmation:
Do you want to perform these actions?
After typing yes, Terraform created the Azure Resource
Group.
Verifying in Azure
After the apply completed, I verified the resource group in the Azure Portal. The resource group existed with the expected tags:
environment = lab
owner = me
managed_by = terraform
The resource group contained no resources, which was expected. At this stage, Terraform had created only the management container.
Terraform State
After the first successful apply, Terraform created a local state file and began tracking the resource.
terraform state list
Expected output:
azurerm_resource_group.lab
This means Terraform understands that the resource block in
main.tf maps to the real Azure Resource Group.
Updating the Resource
Next, I added a new tag to the resource group:
tags = {
environment = "lab"
owner = "me"
managed_by = "terraform"
purpose = "first-terraform-lab"
}
Then I ran:
terraform fmt
terraform plan
Terraform detected that the resource group already existed and only needed an in-place update:
Plan: 0 to add, 1 to change, 0 to destroy.
The plan showed the new tag being added:
+ "purpose" = "first-terraform-lab"
After applying the change, the Azure Portal showed the new tag.
Portal Drift Test
To test Terraform drift detection, I manually changed the
purpose tag in the Azure Portal.
Then I ran:
terraform plan
Terraform detected that Azure no longer matched the configuration:
~ "purpose" = "changed it" -> "first-terraform-lab"
The plan showed:
Plan: 0 to add, 1 to change, 0 to destroy.
Terraform was not going to recreate the resource group. It was only going to correct the tag and bring Azure back in line with the code.
Key Takeaways
This lab demonstrated the basic Terraform workflow:
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
terraform state list
It also demonstrated the core Terraform value:
Code defines the desired state.
Terraform compares desired state to real infrastructure.
Terraform shows the difference before making changes.
Terraform can detect and correct portal drift.
The safest Terraform habit is to slow down and read the plan.
In this lab, the safe plan patterns were:
Plan: 1 to add, 0 to change, 0 to destroy.
Plan: 0 to add, 1 to change, 0 to destroy.
No changes. Your infrastructure matches the configuration.
In future labs, the thing to watch for is unexpected destroy or replace behavior.
Cleanup
When finished, the lab can be destroyed with:
terraform destroy
Terraform will show a destroy plan and ask for confirmation. For this lab, destruction removes only the Terraform-managed resource group.
Final Thoughts
This was a small lab, but it covered the real Terraform loop: write, format, validate, plan, apply, verify, detect drift, and reconcile.
The resource created was intentionally simple. The learning value came from understanding how Terraform thinks, how state works, and how infrastructure changes become reviewable before they are applied.
That is the foundation for moving from manual Azure administration to infrastructure as code.