Filters
How to search
  • Plain words find names, texts and examples.
  • "two words" finds the exact phrase.
  • --name or -x finds an option.
  • #word looks only inside commands.
  • @grep finds lessons and labs that use grep.
  • Filters: cat:network · in:labs · in:notes · level:rhcsa · ch:8 · root:yes. Put ! before a word to leave it out. OR between words means one of them.
  • ↑ ↓ to move · Enter to open · Esc to close · / to search

Chapter 12Name in the course: Hands-on project

Build a staging environment on Azure with Terraform

Put every objective together in one real project: a staging environment for a web shop on Azure, with modules, remote state, variables per environment, security scans and a GitHub Actions pipeline.

  • 5 Lessons
  • 2 Commands

$ run as a normal user# run as root (or with sudo)

12.1

Lay out the repository

Separate reusable modules from the environments that use them.

Repository layout
infra-shop/
├── modules/
│   ├── network/        (VNet, subnets, NSGs)
│   ├── web/            (VMs or App Service, load balancer)
│   └── data/           (PostgreSQL, storage, Key Vault)
├── envs/
│   ├── staging/        (main.tf, backend.tf, staging.tfvars)
│   └── prod/
├── .github/workflows/terraform.yml
└── README.md

12.2

Build the state backend first

The storage for the state is created once, by hand or by a small separate configuration.

az group create -n rg-tfstate -l westeurope
az storage account create -g rg-tfstate -n sttfstateshop --sku Standard_ZRS --min-tls-version TLS1_2 --allow-blob-public-access false
az storage account blob-service-properties update -g rg-tfstate --account-name sttfstateshop --enable-versioning true --enable-delete-retention true --delete-retention-days 30
az storage container create --account-name sttfstateshop -n tfstate --auth-mode login

Commands in this lesson

12.3

Write the staging environment

The environment calls the modules with its own values.

envs/staging/main.tf
module "network" {
  source        = "../../modules/network"
  name          = "vnet-shop-staging"
  address_space = ["10.40.0.0/16"]
  subnets       = { web = "10.40.1.0/24", data = "10.40.2.0/24" }
  tags          = local.tags
}
 
module "web" {
  source     = "../../modules/web"
  env        = "staging"
  subnet_id  = module.network.subnet_ids["web"]
  vm_count   = 2
  vm_size    = "Standard_B2s"
  tags       = local.tags
}
 
module "data" {
  source    = "../../modules/data"
  env       = "staging"
  subnet_id = module.network.subnet_ids["data"]
  tags      = local.tags
}

12.4

Add the pipeline

On a pull request: format, validate, scan, plan (posted as a comment). On merge to main: apply the reviewed plan, with an approval step.

.github/workflows/terraform.yml (short)
on:
  pull_request: { paths: ["envs/**", "modules/**"] }
  push: { branches: [main], paths: ["envs/**", "modules/**"] }
permissions: { id-token: write, contents: read, pull-requests: write }
jobs:
  plan:
    runs-on: ubuntu-latest
    defaults: { run: { working-directory: envs/staging } }
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - uses: azure/login@v2
        with: { client-id: "${{ vars.AZURE_CLIENT_ID }}", tenant-id: "${{ vars.AZURE_TENANT_ID }}", subscription-id: "${{ vars.AZURE_SUBSCRIPTION_ID }}" }
      - run: terraform fmt -check -recursive ../..
      - run: terraform init
      - run: terraform validate
      - run: trivy config --exit-code 1 ../..
      - run: terraform plan -out=tfplan -var-file=staging.tfvars

The full pipeline with OIDC and environments is in Deploy to Azure with OIDC.

12.5

Prove that it works

The project is done when the whole environment can be destroyed and rebuilt from Git without manual steps.

  • [ ] terraform destroy then a pipeline run rebuilds everything.
  • [ ] A change goes through a PR with plan output and a passing security scan.
  • [ ] No secrets in Git; the database password comes from Key Vault.
  • [ ] A nightly drift check (plan -detailed-exitcode) alerts on manual changes.
  • [ ] The README has a diagram, the design decisions and the run commands.

All commands in this chapter