What is infrastructure as code, and what does it not solve?
Defining infrastructure in files makes it reviewable and repeatable. It also introduces state, drift, and a new place for secrets, and none of those disappears because the code is in git.
On this page
- What we'll cover
- What it actually changes
- Declarative and imperative
- State, and why it governs everything
- Drift, which you cannot see by default
- What it does not solve
- Modules, and the abstraction trap
- Secrets, which do not belong in the files
- Adopting it on infrastructure that already exists
- Automating around it with CodeWords
- What to get right first
- Frequently asked questions
- Related reading
Infrastructure as code means your servers, networks, and managed services are defined in files that live in version control, and creating or changing them happens by applying those files rather than by clicking in a console.
The benefits are the ones you would expect from putting anything in version control: it is reviewable, repeatable, and it tells you what should exist. What is less discussed is that it introduces problems of its own — a state to protect, drift to detect, and the fact that the files describing your infrastructure are now a target.
What we'll cover
- What it actually changes
- Declarative and imperative
- State, and why it governs everything
- Drift, which you cannot see by default
- What it does not solve
- Modules, and the abstraction trap
- Secrets, which do not belong in the files
- Adopting it on infrastructure that already exists
- Automating around it with CodeWords
- What to get right first
- Frequently asked questions
What it actually changes
Infrastructure becomes reviewable. A change is a diff somebody can read and object to before it happens, which is the single largest improvement.
Environments become comparable. Staging and production differing becomes a question you can answer by reading two files rather than by clicking through two consoles.
Recreating becomes possible. Rebuilding an environment is running a command rather than remembering what somebody did eighteen months ago.
History exists. What changed, when, and who approved it, for infrastructure, which previously lived nowhere.
Knowledge leaves the individual. The configuration is written down rather than resident in whoever set it up.
What does not change is that somebody still has to understand the infrastructure. The files describe it; they do not explain it.
Declarative and imperative
Declarative definitions describe the desired end state and the tool works out how to get there. Most modern infrastructure tooling works this way.
Imperative scripts describe the steps, which is straightforward to write and produces different results depending on what existed before.
Declarative is what you want for infrastructure, because "this should exist, with these properties" is idempotent by construction — running it twice changes nothing the second time.
Configuration management is a related but distinct problem: what is installed and configured inside a machine, rather than what machines exist. Container images have absorbed much of this.
The declarative model has a cost: when the tool's plan is wrong or surprising, understanding why is harder than reading a script, because the steps were derived rather than written.
State, and why it governs everything
The tool keeps a record of what it created, mapping the definitions in your files to the real resources.
Without it there is no mapping. The tool cannot know that this block of configuration corresponds to that running server.
It must be shared and locked. Two people applying simultaneously against the same state corrupts it, which is why remote state with locking is the first thing to configure.
It frequently contains secrets. Values marked sensitive in the configuration are typically stored in plain form in state, so the storage needs encryption and restricted access.
Losing it does not delete your infrastructure — it means rebuilding the mapping by importing resources one at a time, which is slow and error-prone.
Never edit it by hand except through the tool's own commands, and take a backup before doing even that.
Drift, which you cannot see by default
Drift is the gap between the files and reality, and it accumulates for ordinary reasons.
Somebody changed something in the console, usually during an incident, usually correctly, and did not go back to codify it.
A provider changed a default, so a resource has an attribute nobody set.
Another automation modified a managed resource.
Nothing reports any of this. The files say one thing, reality says another, and the next apply will either revert somebody's fix or produce a surprising plan.
A scheduled plan is the detection mechanism. Running it regularly and reporting non-empty results converts an invisible divergence into a weekly list, while somebody still remembers why they made the change.
What it does not solve
Worth stating, because infrastructure as code gets credited with more than it does.
It does not make your architecture good. A badly designed system defined in files is a badly designed system with better change control.
It does not prevent outages. It makes changes reviewable, which helps, and a reviewed change can still be wrong.
It does not give you disaster recovery. Recreating infrastructure is not recovering data, and the data is the hard part.
It does not remove the need to understand the platform. You still need to know what a subnet is; you now also need to know how the tool expresses one.
It does not stop console changes, unless you also remove console access, which most organisations do not and probably should not during an incident.
Modules, and the abstraction trap
Shared modules reduce duplication and encode the right defaults, which is genuinely valuable across many environments.
They also hide what is happening. A module with forty inputs is a framework, and debugging through it is harder than reading the resources directly.
Version them, and pin the version, or a module change propagates to everything on the next apply.
Start concrete. Write the resources directly, notice the repetition, then extract a module — the same order that works for any abstraction.
Resist the platform team building a module for everything. An abstraction over a cloud provider's abstraction is two layers to learn instead of one, and the second rarely pays for itself.
Secrets, which do not belong in the files
Never commit credentials, which is obvious and still happens constantly.
Reference a secret store and let the tool fetch at apply time, rather than passing values in.
Remember state. Even when the files are clean, secrets frequently land in state in plain form, which is the part people forget.
Restrict who can read state, with the same seriousness as who can read the secrets themselves.
Rotate after exposure. A credential that was in a repository is compromised regardless of whether the commit was removed, because history persists in clones.
Scope the tool's own credentials. The identity applying infrastructure changes is typically extremely privileged, and it is worth limiting what it can reach per environment.
Adopting it on infrastructure that already exists
Almost nobody starts from nothing, and the migration path matters more than the tooling choice.
Import rather than recreate. Bringing existing resources under management preserves what is running; recreating them is an outage.
Import is slow and manual on most tooling — resource by resource, matching configuration to reality — so plan it as a project rather than an afternoon.
Start with one environment, ideally a non-production one, and get the workflow right before touching anything that matters.
Start with new resources. Everything created from now on is defined in files, which stops the untracked estate growing while you work through the existing one.
Accept partial coverage for a while. A mixture of managed and unmanaged resources is normal during migration, and pretending otherwise leads to plans that want to delete things.
Inventory what is not covered, explicitly, so the gap is known rather than discovered when somebody assumes everything is in code.
Automating around it with CodeWords
The valuable automation around infrastructure as code is mostly reporting — drift across many workspaces, cost, compliance — which is not something a pipeline attached to one repository can do. CodeWords connects to more than 3,000 integrations.
- Open CodeWords and start a new automation.
- Describe what should happen in plain language to Cody, the automation builder: which workspaces, what to check, and who should be told.
- Describe the exceptions: a plan that fails, a workspace already locked, a change involving destruction.
- Connect with read-only credentials for anything that only reports.
You describe the outcome; Cody builds it, connects it, and deploys it. The free plan covers light use, with Pro at $39 per month and Business at $100 per month as usage grows; details are on the pricing page.
What to get right first
Remote state with locking, before anything else. It is a small piece of setup, it prevents the one failure that is genuinely painful to recover from, and every team that skips it discovers why the first time two people apply at once.
Two habits make the difference. Treat state as containing secrets, because it usually does, and restrict access accordingly. And schedule a plan that nobody applies, weekly, since it is the only thing that surfaces the console change somebody made during an incident and forgot to codify.
Frequently asked questions
What is state and why does it matter?
The tool's record of which configuration corresponds to which real resource. Without it there is no mapping, which is why it needs to be remote, locked, backed up, and treated as sensitive — it usually contains secrets in plain form.
What is drift?
The gap between your files and reality, caused by console changes, provider default changes, or other automation. Nothing reports it by default, which is why a scheduled plan that nobody applies is the standard detection mechanism.
Should automation apply changes?
After review and approval, reasonably. On a schedule with nobody involved, no — and destroys should always require explicit approval, ideally from somebody other than the author.
Does this give us disaster recovery?
Only for the infrastructure, not the data. Recreating servers and networks from files is genuinely useful and it is not recovery, because restoring the data is the difficult and separate half.
Should everything be in modules?
No. Write resources directly, notice the repetition, then extract. A module with forty inputs is a framework, and debugging through an unnecessary abstraction costs more than the duplication it removed.
Can we stop people changing things in the console?
You can, and during an incident you probably should not want to. The realistic approach is to accept console changes happen and detect them, then codify them afterwards while somebody still remembers the reason.
Where should secrets live?
In a secret store, fetched at apply time, never in the files. And remember that values typically end up in state in plain form regardless, so state access control is part of the same question.
How do I adopt this on infrastructure that already exists?
Import rather than recreate, start with a non-production environment, and require everything new to be defined in files so the untracked estate stops growing. Import is slow and manual, so treat it as a project.
Is partial coverage a problem?
It is normal during migration, and it becomes a problem when people assume coverage is complete. Keep an explicit inventory of what is not managed, or somebody will eventually read a plan as authoritative when it describes half the estate.
What identity should the tool run as?
One scoped per environment, not a single highly privileged credential shared across all of them. The applying identity is typically among the most powerful in the account, and limiting its reach per environment is the cheapest available containment.