Enterprise Azure Terraform Lab Phase 1 Part 1: Foundation and Remote State
๐ GitHub Repository: lwc-wk/enterprise-azure-lab1
Enterprise Azure Terraform Lab Phase 1 Part 1: Foundation and Remote State
๐งฉ Background
This lab is not about building a fancy frontend application.
The real goal is to prove that I can design, deploy, explain, validate, and clean up Azure infrastructure using Terraform.
The core idea is simple:
Fake UI is acceptable. Fake architecture is not.
The React dashboard can stay simple. The infrastructure behind it must be real, explainable, repeatable, and cost-aware.
This Phase 1 lab simulates a small enterprise-style Azure environment for an internal business application. It focuses on:
- Resource organization
- Terraform Infrastructure as Code
- Remote state
- Virtual networking
- Subnet segmentation
- Storage Account security
- Monitoring foundation
- App Service hosting
- Budget alerts
- Basic Azure RBAC
- Destroy-after-validation discipline

๐ง Design Philosophy
Before writing Terraform, I defined the engineering principles first.
Least Privilege
Nobody should receive more access than required.
In Phase 1, this means avoiding broad Owner or Contributor access unless absolutely necessary.
Segmentation
Different functions should be separated.
Even though this is a low-cost lab, the network is designed with separate subnets:
- Application subnet
- Management subnet
This keeps the design simple while still showing enterprise-style thinking.
Auditability
I should be able to explain:
- What exists
- Why it exists
- Who can access it
- How it is monitored
- How it would change in production
Repeatability
The lab must be deployable, testable, and destroyable with Terraform.
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
terraform destroy
Low Cost
This lab intentionally avoids unnecessary expensive services.
Phase 1 does not use:
- Virtual Machines
- Bastion
- Load Balancer
- Azure Firewall
- Always-on paid tiers unless temporarily needed
Paid tiers are only used briefly for validation screenshots, then scaled down or destroyed.
๐๏ธ Architecture Model
The lab is split into two layers.
Permanent Layer
This layer stores Terraform state and should normally stay alive.
It is created manually first with Azure CLI.
rg-tfstate-enterprise-lab
โโโ Storage Account
โโโ Blob container: tfstate
Why this exists:
- Terraform needs a safe place to store state.
- Local
terraform.tfstateis fragile. - The backend should survive lab destroy operations.
- It separates long-lived state infrastructure from short-lived lab infrastructure.

Ephemeral Lab Layer
This is the actual portfolio lab.
It can be deployed when needed and destroyed afterward.
rg-infra-dev-azlab2
โโโ Virtual Network
โ โโโ subnet-app
โ โโโ subnet-management
โโโ Network Security Group
โโโ Storage Account
โโโ Log Analytics Workspace
โโโ App Service Plan
โโโ Azure Linux Web App
โโโ Diagnostic Settings
โโโ Resource Group Budget
โโโ Optional RBAC Assignments
This layer is designed to be disposable.

๐ Repository Structure
The repository is organized to separate app code, Terraform code, environment variables, screenshots, and helper scripts.
enterprise-azure-terraform-lab/
โโโ README.md
โโโ architecture.md
โโโ SOUL.md
โโโ app/
โ โโโ React + Vite + TypeScript dashboard
โโโ docs/
โ โโโ screenshots/
โ โโโ diagrams/
โโโ env/
โ โโโ dev.tfvars
โ โโโ demo.tfvars
โ โโโ local.secrets.tfvars # not committed
โ โโโ local.rbac.tfvars # not committed
โโโ infra/
โ โโโ backend.tf
โ โโโ versions.tf
โ โโโ providers.tf
โ โโโ locals.tf
โ โโโ generic-input-variables.tf
โ โโโ random-resources.tf
โ โโโ resource-group.tf
โ โโโ network.tf
โ โโโ security.tf
โ โโโ storage.tf
โ โโโ monitoring.tf
โ โโโ diagnostics.tf
โ โโโ app-service.tf
โ โโโ budget.tf
โ โโโ rbac.tf
โ โโโ outputs.tf
โ โโโ autoscale.tf.disabled
โ โโโ app-service-slot.tf.disabled
โโโ scripts/
โโโ init-backend.sh
โโโ deploy-infra.sh
โโโ deploy-app.sh
โโโ destroy.sh
๐งพ Why Split Terraform Files?
The Terraform code is split by responsibility.
| File | Purpose |
|---|---|
versions.tf |
Terraform and provider version requirements |
providers.tf |
AzureRM provider configuration |
backend.tf |
Remote state backend configuration |
generic-input-variables.tf |
External configuration variables |
locals.tf |
Naming prefix and common tags |
random-resources.tf |
Random suffixes for globally unique names |
resource-group.tf |
Azure Resource Group |
network.tf |
VNet and subnets |
security.tf |
NSG and subnet associations |
storage.tf |
Storage Account |
monitoring.tf |
Log Analytics Workspace |
diagnostics.tf |
Diagnostic settings |
app-service.tf |
App Service Plan and Linux Web App |
budget.tf |
Resource Group budget alert |
rbac.tf |
Optional Azure RBAC role assignments |
outputs.tf |
Terraform outputs |
This makes the code easier to understand, maintain, and explain in an interview.
๐งฑ Terraform Backend
Terraform backend is created manually first.
The backend uses:
- Resource Group
- Storage Account
- Blob container
Example backend configuration:
terraform {
backend "azurerm" {
resource_group_name = "rg-tfstate-enterprise-lab"
storage_account_name = "sttfstatexxxxx"
container_name = "tfstate"
key = "enterprise-azure-lab-dev.tfstate"
}
}
Important lesson:
The backend is not part of the destroyable lab layer.
The backend Resource Group should stay:
rg-tfstate-enterprise-lab
The lab Resource Group can be destroyed:
rg-infra-dev-azlab2
๐ท๏ธ Variables, Locals, and Naming
Environment-specific values are placed in env/dev.tfvars.
business_division = "infra"
environment = "dev"
project_name = "azlab2"
resource_group_location = "centralus"
owner = "lw"
monthly_budget_amount = 10
budget_start_date = "2026-05-01T00:00:00Z"
During testing, the first Resource Group had partial deployment and App Service capacity issues. Changing the project name to azlab2 forced Terraform to create a clean resource naming set.
Example naming pattern:
infra-dev-azlab2
Resources become:
rg-infra-dev-azlab2
vnet-infra-dev-azlab2
nsg-infra-dev-azlab2
law-infra-dev-azlab2
asp-infra-dev-azlab2
app-infra-dev-azlab2-xxxxxx
Common tags are used for governance and cost awareness.
common_tags = {
Project = "enterprise-azure-lab"
Environment = var.environment
Owner = var.owner
ManagedBy = "terraform"
CostControl = "destroy-after-validation"
}
๐งน Resource Group as Lifecycle Boundary
The Resource Group is the lifecycle boundary of the lab.
It groups all ephemeral lab resources together.
This makes cleanup simple:
terraform destroy -var-file="../env/dev.tfvars"
๐งพ Summary
In this first part, I built the foundation of the lab:
- Defined the architecture principles
- Separated permanent backend resources from disposable lab resources
- Created a Terraform remote state backend
- Organized Terraform files by responsibility
- Standardized naming and tags
- Used the Resource Group as the lifecycle and cost boundary
This is the base layer. Without this, the rest of the lab would just be random Azure clicking with better lighting.