Learn / Infrastructure and deployment
Infrastructure as code
Lesson 24 of 37 · 8 min read ·
The problem
You clicked through a cloud console for two hours and got a working environment. Six months later nobody remembers which of the forty settings mattered, staging behaves differently from production for reasons no one can name, and the person who set it up has left.
Infrastructure as code means the environment is defined in files, in version control, and applied by a tool. The value is not automation — it is that infrastructure becomes reviewable, diffable, reproducible and revertible, exactly like application code.
Declarative beats imperative
Imperative — a script of steps: create this, then that. It works once. Run it again and you get duplicates or errors, and it cannot tell you what already exists.
Declarative — you describe the desired end state; the tool computes the difference and applies it. Running it twice changes nothing the second time. That idempotence is the whole point.
Terraform, OpenTofu, Bicep, CloudFormation and Pulumi are all declarative. Terraform is the default for multi-cloud; Bicep is pleasant if you are entirely on Azure; Pulumi lets you use a real programming language, which is both its appeal and its risk.
State and drift
Declarative tools keep a state file mapping your configuration to real resources. It is how the tool knows that azurerm_app_service.web is that specific existing app service rather than a new one to create.
Three rules follow, and each one is a lesson people learn painfully:
- State goes in remote shared storage with locking — an S3 bucket with DynamoDB locking, an Azure storage account, Terraform Cloud. Local state means only one person can apply, and losing the file means the tool no longer knows what it owns.
- State can contain secrets in plain text. Encrypt it, restrict access to it, and never commit it.
- Drift is when reality diverges from state — someone made an emergency change in the console at 2 a.m. The next apply will try to revert it, sometimes destructively. Run
planregularly to detect drift, and treat any manual change as something to bring back into code the same week.
Read the plan
terraform plan prints exactly what will be created, changed and destroyed. Read it, every time.
The dangerous line is # forces replacement — some attribute changes cannot be applied in place, so the tool will destroy and recreate the resource. On a database that is data loss. This should be a required review step in your pipeline: plan on the pull request, apply on merge, with a human looking at the destroy count.
Adopting it on existing infrastructure
You almost never start clean. The workable path:
- Start with something new and low-risk — a staging environment — so mistakes are cheap.
- Import critical existing resources gradually (
terraform import, or generated import blocks). - Split state by lifecycle: networking, data, applications. One giant state file makes every change slow and every mistake wide.
- Modules for anything you build more than once. But do not abstract early — a copied module you later parameterise beats a wrong abstraction.
- When everything for a component is in code, remove console write access for it. Otherwise drift returns immediately.
Gotchas
- Never
applyfrom a laptop against production. Run it in CI with reviewed plans and an audit trail. - Pin provider and module versions. An unpinned provider upgrade can rewrite your plan overnight.
- Keep secrets out of
.tffiles — reference a secrets manager. See secrets management. - Tag everything with owner, environment and cost centre. You will need it the first time someone asks why the bill went up.
- Set
prevent_destroyon databases and anything else you cannot recreate.
Prove you know it
Define one real piece of infrastructure in code, apply it, delete it in the console, and run the tool again to bring it back. That round trip — code, destroy, restore — is the actual capability. Then answer: if your cloud account were emptied tomorrow, how much could you rebuild from this repository?