{"course":{"slug":"cloud-and-shipping","title":"Shipping It","url":"https://addaly.com/learn/cloud-and-shipping","level":"intermediate","track":"build"},"lesson":{"slug":"infrastructure-you-can-recreate","title":"The console clicks nobody wrote down","url":"https://addaly.com/learn/cloud-and-shipping/infrastructure-you-can-recreate","position":14,"of":74,"minutes":9,"answer":"Infrastructure created by clicking is a design nobody wrote down; put it in files so you get a diff, a rebuild and a history.","keyIdea":"Infrastructure created by clicking is a design nobody wrote down; put it in files so you get a diff, a rebuild and a history, and treat the plan output as the review step where a replace means a new empty database."},"body":{"format":"markdown","text":"## How infrastructure actually gets built\n\nYou click through a provider's console at eleven at night. A database, a firewall rule, an environment variable, a cron job, a DNS record, a queue, a bucket with the public flag turned off. It works, you go to bed, and the site runs for a year.\n\nThen one of four things happens. You need a second environment and cannot remember what the first one has. Somebody deletes something and nobody knows what it was for. The provider has an outage in that region and you would like the same thing somewhere else. Or you leave, and the person after you inherits a machine nobody can describe.\n\nThe clicks were the design. They were never written down.\n\n## What infrastructure as code actually buys\n\nWriting the infrastructure as files in your repository gives you four things, and it is worth being precise about them because the marketing is vague:\n\n1. **A diff.** You can see that this change opens port 5432 to the world before it does.\n2. **A recreate.** The environment can be built again from nothing, which is also your disaster recovery plan.\n3. **A record.** Git history says who added the bucket, when, and in which pull request they explained why.\n4. **A second environment for almost nothing.** Staging becomes the same file with different variables.\n\nWhat it does not buy is safety from mistakes. A typo in a Terraform file can destroy a database far faster than a typo in a console, because the console asks and the tool does not.\n\n## The tools, and the free paths\n\n**Terraform** is the industry standard and the one job ads name. In 2023 its licence changed from open source to the Business Source Licence, which triggered a fork: **OpenTofu**, now under the Linux Foundation, MPL-licensed, and a drop-in replacement for most projects. Both are free to use for the work you are doing here; if you want a licence with no ambiguity, use OpenTofu and the same `.tf` files.\n\n**Pulumi** lets you write the same thing in TypeScript or Python, which suits people who find a new declarative language annoying. **Ansible** is the neighbouring tool for configuring a machine you already have, rather than creating it. **Docker Compose** counts too: for a single box, one committed `compose.yml` is a real, honest version of this idea and might be all you need for years.\n\nAnd the smallest version that still works: a shell script called `provision.sh` in the repository, with the exact commands, run against a fresh machine once to prove it does. This is not a lesser answer for a small project. It is the answer that gets written.\n\n## The state file, which is the part that bites\n\nTerraform and OpenTofu keep a state file mapping your code to real resources. Three facts about it, all learned the hard way:\n\n- **It contains secrets.** Database passwords end up in plain text. Never commit it to git.\n- **Two people applying at once will corrupt it.** Use a remote backend with locking: an S3-compatible bucket plus DynamoDB, Terraform Cloud's free tier, or a Postgres backend.\n- **Deleting a resource from the file means deleting it in the world.** Read the plan. Every time.\n\nThat last point is why `plan` before `apply` is not a formality. The plan output names each resource and marks it as create, update, or destroy, and the ones that say *replace* are the dangerous ones, because replacing a database means a new empty database.\n\n```bash\ntofu plan -out=tfplan     # read this output line by line\ntofu apply tfplan\n```\n\nWire `plan` into CI on every pull request so the diff is reviewed by a person before it exists.\n\n## Drift, and the honest compromise\n\nSomebody will fix something in the console at three in the morning during an outage. That is correct behaviour: the site matters more than the process. What must not happen is that the change stays only in the console, so the next `apply` silently reverts it.\n\nThe habit is: fix it by hand, then write it into the file within a day, then run `plan` and confirm it reports no changes. `terraform plan` with an empty diff is the check that your code and your world still agree, and running it on a schedule turns drift into a notification rather than a surprise.\n\n## What to put under it first\n\nDo not try to codify everything. Start with the parts that are hard to remember and expensive to lose: DNS records, firewall and security-group rules, the database and its backup settings, buckets and their access policies, and the environment variables' *names* (not their values, which stay in a secret manager).\n\nLeave the rest clicked for now, and write a one-line note in the repository saying so. A partially codified system with an honest map beats a fully codified system that nobody maintains."},"check":{"question":"During an outage a colleague opens the console and widens a firewall rule to restore service. The rule is not added to the Terraform files. What happens on the next `apply`, and why?","options":[{"text":"The rule is preserved, because Terraform only manages resources it created","correct":false,"tempting":"True for resources outside its state, but this rule belongs to a security group under management, so it falls inside the desired state Terraform enforces."},{"text":"The apply fails with a conflict, forcing somebody to reconcile the difference","correct":false,"tempting":"Assumes the tool refuses on drift. It does not: it treats the files as the truth and quietly plans the world back towards them."},{"text":"The rule is reverted, because the files describe the desired state and the world is corrected to match","correct":true},{"text":"The state file is updated to include the manual change, since refresh reads reality first","correct":false,"tempting":"Refresh does read reality, but only to compute the difference: the difference is then removed, not adopted."}]},"prev":{"slug":"a-pipeline-you-trust","title":"The checks that run without you","url":"https://addaly.com/learn/cloud-and-shipping/a-pipeline-you-trust"},"next":{"slug":"deploying-small","title":"Deploying for nearly nothing","url":"https://addaly.com/learn/cloud-and-shipping/deploying-small"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The console clicks nobody wrote down', in Shipping It. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/cloud-and-shipping/infrastructure-you-can-recreate (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The console clicks nobody wrote down","courseTitle":"Shipping It","module":{"index":2,"slug":"making-a-deploy-boring","title":"Making a deploy boring"},"answer":null,"keyIdea":"Infrastructure created by clicking is a design nobody wrote down; put it in files so you get a diff, a rebuild and a history, and treat the plan output as the review step where a replace means a new empty database.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"how-infrastructure-actually-gets-built","runs":[{"text":"How infrastructure actually gets built"}]},{"t":"p","runs":[{"text":"You click through a provider’s console at eleven at night. A database, a firewall rule, an environment variable, a cron job, a DNS record, a queue, a bucket with the public flag turned off. It works, you go to bed, and the site runs for a year."}]},{"t":"p","runs":[{"text":"Then one of four things happens. You need a second environment and cannot remember what the first one has. Somebody deletes something and nobody knows what it was for. The provider has an outage in that region and you would like the same thing somewhere else. Or you leave, and the person after you inherits a machine nobody can describe."}]},{"t":"p","runs":[{"text":"The clicks were the design. They were never written down."}]},{"t":"h2","id":"what-infrastructure-as-code-actually-buys","runs":[{"text":"What infrastructure as code actually buys"}]},{"t":"p","runs":[{"text":"Writing the infrastructure as files in your repository gives you four things, and it is worth being precise about them because the marketing is vague:"}]},{"t":"ol","start":1,"items":[[{"text":"A diff.","b":true},{"text":" You can see that this change opens port 5432 to the world before it does."}],[{"text":"A recreate.","b":true},{"text":" The environment can be built again from nothing, which is also your disaster recovery plan."}],[{"text":"A record.","b":true},{"text":" Git history says who added the bucket, when, and in which pull request they explained why."}],[{"text":"A second environment for almost nothing.","b":true},{"text":" Staging becomes the same file with different variables."}]]},{"t":"p","runs":[{"text":"What it does not buy is safety from mistakes. A typo in a Terraform file can destroy a database far faster than a typo in a console, because the console asks and the tool does not."}]},{"t":"h2","id":"the-tools-and-the-free-paths","runs":[{"text":"The tools, and the free paths"}]},{"t":"p","runs":[{"text":"Terraform","b":true},{"text":" is the industry standard and the one job ads name. In 2023 its licence changed from open source to the Business Source Licence, which triggered a fork: "},{"text":"OpenTofu","b":true},{"text":", now under the Linux Foundation, MPL-licensed, and a drop-in replacement for most projects. Both are free to use for the work you are doing here; if you want a licence with no ambiguity, use OpenTofu and the same "},{"text":".tf","code":true},{"text":" files."}]},{"t":"p","runs":[{"text":"Pulumi","b":true},{"text":" lets you write the same thing in TypeScript or Python, which suits people who find a new declarative language annoying. "},{"text":"Ansible","b":true},{"text":" is the neighbouring tool for configuring a machine you already have, rather than creating it. "},{"text":"Docker Compose","b":true},{"text":" counts too: for a single box, one committed "},{"text":"compose.yml","code":true},{"text":" is a real, honest version of this idea and might be all you need for years."}]},{"t":"p","runs":[{"text":"And the smallest version that still works: a shell script called "},{"text":"provision.sh","code":true},{"text":" in the repository, with the exact commands, run against a fresh machine once to prove it does. This is not a lesser answer for a small project. It is the answer that gets written."}]},{"t":"h2","id":"the-state-file-which-is-the-part-that-bites","runs":[{"text":"The state file, which is the part that bites"}]},{"t":"p","runs":[{"text":"Terraform and OpenTofu keep a state file mapping your code to real resources. Three facts about it, all learned the hard way:"}]},{"t":"ul","items":[[{"text":"It contains secrets.","b":true},{"text":" Database passwords end up in plain text. Never commit it to git."}],[{"text":"Two people applying at once will corrupt it.","b":true},{"text":" Use a remote backend with locking: an S3-compatible bucket plus DynamoDB, Terraform Cloud’s free tier, or a Postgres backend."}],[{"text":"Deleting a resource from the file means deleting it in the world.","b":true},{"text":" Read the plan. Every time."}]]},{"t":"p","runs":[{"text":"That last point is why "},{"text":"plan","code":true},{"text":" before "},{"text":"apply","code":true},{"text":" is not a formality. The plan output names each resource and marks it as create, update, or destroy, and the ones that say "},{"text":"replace","i":true},{"text":" are the dangerous ones, because replacing a database means a new empty database."}]},{"t":"code","lang":"bash","label":"Bash","text":"tofu plan -out=tfplan     # read this output line by line\ntofu apply tfplan"},{"t":"p","runs":[{"text":"Wire "},{"text":"plan","code":true},{"text":" into CI on every pull request so the diff is reviewed by a person before it exists."}]},{"t":"h2","id":"drift-and-the-honest-compromise","runs":[{"text":"Drift, and the honest compromise"}]},{"t":"p","runs":[{"text":"Somebody will fix something in the console at three in the morning during an outage. That is correct behaviour: the site matters more than the process. What must not happen is that the change stays only in the console, so the next "},{"text":"apply","code":true},{"text":" silently reverts it."}]},{"t":"p","runs":[{"text":"The habit is: fix it by hand, then write it into the file within a day, then run "},{"text":"plan","code":true},{"text":" and confirm it reports no changes. "},{"text":"terraform plan","code":true},{"text":" with an empty diff is the check that your code and your world still agree, and running it on a schedule turns drift into a notification rather than a surprise."}]},{"t":"h2","id":"what-to-put-under-it-first","runs":[{"text":"What to put under it first"}]},{"t":"p","runs":[{"text":"Do not try to codify everything. Start with the parts that are hard to remember and expensive to lose: DNS records, firewall and security-group rules, the database and its backup settings, buckets and their access policies, and the environment variables’ "},{"text":"names","i":true},{"text":" (not their values, which stay in a secret manager)."}]},{"t":"p","runs":[{"text":"Leave the rest clicked for now, and write a one-line note in the repository saying so. A partially codified system with an honest map beats a fully codified system that nobody maintains."}]}],"check":{"question":[{"text":"During an outage a colleague opens the console and widens a firewall rule to restore service. The rule is not added to the Terraform files. What happens on the next "},{"text":"apply","code":true},{"text":", and why?"}],"options":[{"text":[{"text":"The rule is preserved, because Terraform only manages resources it created"}],"correct":false,"tempting":[{"text":"True for resources outside its state, but this rule belongs to a security group under management, so it falls inside the desired state Terraform enforces."}]},{"text":[{"text":"The apply fails with a conflict, forcing somebody to reconcile the difference"}],"correct":false,"tempting":[{"text":"Assumes the tool refuses on drift. It does not: it treats the files as the truth and quietly plans the world back towards them."}]},{"text":[{"text":"The rule is reverted, because the files describe the desired state and the world is corrected to match"}],"correct":true,"tempting":null},{"text":[{"text":"The state file is updated to include the manual change, since refresh reads reality first"}],"correct":false,"tempting":[{"text":"Refresh does read reality, but only to compute the difference: the difference is then removed, not adopted."}]}],"order":[0,2,3,1],"why":null},"prev":{"slug":"a-pipeline-you-trust","title":"The checks that run without you","minutes":9},"next":{"slug":"deploying-small","title":"Deploying for nearly nothing","minutes":8}}}