DEV Community

Samuel Ojo
Samuel Ojo

Posted on

Cloud & DevOps Wk 3: Installing Terraform, migrating state to an Azure storage account, and building VNets for my cloud project.

Public cloud engineering project: installing Terraform, creating and destroying Azure resources from code, moving state into an Azure storage account, and building my first VNet.

Week 3: Terraform on Azure, remote state, and a scrambled paste

I'm writing a series about cloud and infrastructure engineering and building one project in public, one week at a time. Week 1 was Linux and Git, and Week 2 was Bash scripting and networking. Week 3 was Terraform, which I did in two parts: a first resource group, then remote state and a first virtual network.

Part 1: Installing Terraform and creating my first resource
On my Ubuntu VM I installed Terraform from HashiCorp's APT repository, and the Azure CLI, then signed in without a browser on the VM:

az login --use-device-code
az account show --output table
Enter fullscreen mode Exit fullscreen mode

My first mistake: I ran az account show before installing the Azure CLI and got az: command not found. Install first, then log in.

The first config was deliberately tiny, one resource group:

resource "azurerm_resource_group" "main" {
  name     = "rg-tfdemo-uks-001"
  location = "uksouth"
  tags = {
    environment = "learning"
    owner       = "samuel"
  }
}
Enter fullscreen mode Exit fullscreen mode

The azurerm provider needs to know which subscription to use, so I exported it:

export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
Enter fullscreen mode Exit fullscreen mode

Then the full workflow: terraform init, fmt, validate, plan (1 to add, 0 to change, 0 to destroy), apply, a check in the portal, and destroy.

Terraform destroy completing after the first apply

Keeping secrets out of Git
Before my first commit I added this .gitignore:

.terraform/
*.tfstate
*.tfstate.*
*.tfvars
crash.log
Enter fullscreen mode Exit fullscreen mode

State files can contain secrets and resource IDs, so they never go to GitHub. I checked with git status -u before committing, which lists every untracked file individually. I do commit .terraform.lock.hcl, because it pins the exact provider version.

Part 2: Remote state
By default, Terraform keeps its memory (the state file) on the machine that runs it. That's fine for one person on one laptop, and not for teams. So I moved my state into an Azure storage account.

There's a chicken-and-egg problem: Terraform can't create the storage that holds its own state. So I created it once, by hand, with the Azure CLI, in its own resource group that I never destroy:

az group create -n rg-tfstate-uks-001 -l uksouth --tags purpose=terraform-state

az storage account create -n <unique-name> -g rg-tfstate-uks-001 -l uksouth \
  --sku Standard_LRS --kind StorageV2 --min-tls-version TLS1_2 \
  --allow-blob-public-access false
Enter fullscreen mode Exit fullscreen mode

Storage account names must be unique across all of Azure, so I generated mine with random numbers.

Being the owner of an account doesn't let you read its blobs. I gave my own login the Storage Blob Data Contributor role on that one account, then created a container called tfstate. That means Terraform authenticates with my Azure login and no access keys are involved:

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-tfstate-uks-001"
    storage_account_name = "<unique-name>"
    container_name       = "tfstate"
    key                  = "azure-cloud-native-platform.tfstate"
    use_azuread_auth     = true
  }
}
Enter fullscreen mode Exit fullscreen mode

Splitting the code into files
I also split the config into six files, which is how real projects are organised: providers.tf, backend.tf, variables.tf, main.tf, network.tf and outputs.tf. Names and region come from variables, so the environment can be recreated or renamed in one place.

My first network
Using the subnet maths from Week 2:

  • A VNet with 10.0.0.0/16 (65,536 addresses)
  • A subnet snet-app with 10.0.1.0/24 (251 usable addresses in Azure)
  • A network security group allowing only inbound HTTPS (port 443), attached to the subnet.

terraform init -migrate-state moved my state into Azure, plan showed five resources to add, and apply built them. This is what I checked afterwards:

terraform state list
az storage blob list --account-name <unique-name> --container-name tfstate \
  --auth-mode login --output table
Enter fullscreen mode Exit fullscreen mode

The state file was in Azure, about 8.9 KB while my resources existed. After terraform destroy it dropped to 181 bytes (empty state), and the storage account stayed put. I also saw Acquiring state lock during runs. Azure holds a lease on the state blob, so two people can't apply at once.

Terraform state list and the state file stored in Azure

What went wrong
I pasted a very long block of files into PuTTY in one go, and the paste got scrambled. Characters were dropped and lines were mixed together, leaving garbage such as a line starting EOFalue = ... in the middle of a file.

Terminal showing a scrambled paste

How I recovered:

1.Deleted the broken files and recreated them in smaller pastes, one resource group of code at a time.

2.Read each file back with cat.

3.Let Terraform check my typing: terraform validate returned Success! The configuration is valid.

Terraform init and validate succeeding

Lesson: don't trust a long paste. Check the file, then let the tool validate it.

What I learned

  • Read the plan before you apply. It tells you exactly what will change.
  • State holds secrets, so it goes in .gitignore, and the lock file goes in Git.
  • Remote state, with Azure AD authentication instead of keys, is how teams share state safely, and locking stops two people overwriting each other.
  • Always destroy at the end of a session. I also set a budget alert in Azure before starting.
  • Everything went through a branch and a pull request.

Git log showing the merged pull request

What's next
Week 4: Docker. I'll containerise a small API, then push the image to Azure Container Registry. Then a pipeline in GitHub Actions to build and deploy it.

Repo: link

See you on the next one...

Top comments (0)