
Earlier in my career I was at a small startup. We were in a planning session, talking through how a piece of our deployment should be built and maintained over time.
A senior engineer I worked with had a strong math background and cared about precise definitions. When I used the word idempotent to justify a design choice, he paused and asked me to define it.
I cannot be held responsible for the accuracy of whatever definition I muttered. But that moment stuck. Over time I’ve come to see that getting idempotence right is crucial to both security and platform engineering. And it leads to some unexpected conclusions about the tools commonly in use at technology companies.
What he was getting at
In mathematics, an operation is idempotent if applying it twice is the same as applying it once. For a function f, that’s f(f(x)) = f(x).1
Absolute value is idempotent: |−3| = 3, and |3| = 3. So is a thermostat: set it to 73°, it reaches 73°. Set it to 73° again, it’s still 73°. By contrast, “turn up 2°” is not idempotent, because each press changes the outcome. The operation itself has the property. No “first run vs. second run.” Just: do it once or do it a hundred times, you get the same result.
That’s what my former colleague was pointing at.
What we usually mean in practice
When we talk about infrastructure and automation we say “idempotent” when we usually mean something slightly different: safe to run repeatedly; the outcome stabilizes after the first run.2 We care that re-running a playbook or a pipeline doesn’t double-create resources, double-restart services, or leave the system in a different state than “run it once” would.
So we’re really talking about convergence to a desired state rather than the strict mathematical property that the operation is unchanged by repetition. The distinction is subtle but real. In math, idempotence is a property of the function. In our world, we often achieve “idempotent-like” behavior by comparing current state to desired state and only making changes when they differ. That’s not the same thing as the operation being idempotent in the mathematical sense.
Why it matters
When you have a solid mental model of idempotence, you can reason about:
- Security: Systems that are idempotent (or close) are easier to audit and recover. There’s no “you had to run that script exactly once” or “never run that again” footgun. This matters when you re-run key rotation jobs, replay infrastructure pipelines after partial failure, or recover from an incident under pressure. Repeatability reduces surprise and drift.
- Platform engineering: Predictability. Run the same definition twice, get the same result. No hidden state that only exists in someone’s head or in a state file that didn’t get committed.
The catch: not all of our favorite tools are idempotent in the same way. Some are closer to the math; some are the opposite.
OpenTofu: fragile idempotence
OpenTofu (and Terraform, from which it was forked) is incredibly useful. It also has a complicated relationship with idempotence.
It achieves idempotent-like behavior by keeping track of state that OpenTofu compares against your code to decide what to create, update, or destroy. The language you write is declarative and looks idempotent: “there should be a bucket.” And when the state file is in sync with reality, a second apply is a no-op. But that idempotence is conditional: it depends entirely on an external artifact (the state file) staying accurate. If the state drifts, or is lost, or wasn’t committed, re-running the same config can do something different, or destructive.3 As anyone who has had to debug OpenTofu runs will tell you, much of this is due to the unpredictability of the APIs it integrates with. The operation of “apply this OpenTofu” is not inherently idempotent; its idempotence is only as reliable as the state it tracks.
So in a sense, OpenTofu abstracts away idempotence. It gives you an interface that feels declarative and repeatable, but the mechanism is state-tracking and delta application, and the idempotence breaks when that state is wrong. Useful? Yes. Robustly idempotent? No.
Kubernetes: closer to the math
Kubernetes leans the other way. You submit a definition: “this deployment should exist, with this spec.” The system doesn’t rely on a separate state file that you have to keep in sync with the world. Instead, Kubernetes runs continuous reconciliation loops: controllers observe actual state, compare it to the declared state, and actively push the system back toward the declaration. The definition is the desired state. kubectl apply with the same manifest twice is the same operation with the same outcome. The API is effectively “ensure this exists.” No external state artifact to manage. That’s much closer to idempotent in the mathematical sense: same input (the manifest), same effect, regardless of how many times you apply it.
It’s not perfect. Kubernetes has its own non-idempotent corners: immutable fields that force a delete-and-recreate, kubectl create (which errors on the second run), and resources using generateName (which create a new object every time). And Kubernetes absolutely has state. It just manages it internally in etcd, so the operator doesn’t carry the burden of keeping a state file in sync. But the default posture of kubectl apply is “converge to this,” and the reconciliation loops keep pushing toward it even when things drift. That’s a meaningfully different architecture from one whose idempotence depends on an external artifact you manage yourself.
Ansible, Chef, bash, Python: it depends how you write it
A whole class of tools fits here: Ansible, Chef, Puppet, bash scripts, Python automation, and the like. They can be idempotent, or they can be the opposite. There’s no single answer. The tool doesn’t decide; how you use it does.
Written carefully, an Ansible playbook can behave as an idempotent system more purely than OpenTofu. Modules like package, file, template, service are designed to check current state and only make changes when needed. Run the playbook twice with the same inputs and inventory, and the second run should largely no-op. No separate state file. The tasks are the definition; the run is the operation. That’s much closer to “do it once or do it twice, same result.” Bash and Python have no built-in notion of idempotence; you can write scripts that check before they change and effectively become idempotent, or you can write scripts that append to a file every time, create duplicate records, or assume they run exactly once.4
But all of these can be used in deeply non-idempotent ways: raw shell commands, one-off scripts, “run this and hope you never run it again” patterns. Many playbooks and recipes in the wild are not idempotent at all. So these tools don’t guarantee idempotence; they allow it when you design for it.
The takeaway
Idempotence in math is precise: the operation itself has the property. In infrastructure and automation we’ve stretched the word to mean “safe to run again” and “converges to a desired state.” That’s fine, as long as we know the difference. And strictly speaking, no infrastructure tool is mathematically idempotent. They all interact with external systems that can change between runs. What we’re really after is state-convergent operations: push toward a declared end state regardless of starting point.
When we pick tools and patterns, the sharper question is: who is responsible for the state comparison? OpenTofu delegates it to an external artifact: a state file the operator must keep in sync. Kubernetes delegates it to the platform itself, which self-heals through reconciliation loops. Well-written Ansible embeds it in the operation: each module checks actual state directly, no intermediary artifact. That hierarchy, from operator-managed state to platform-managed state to operation-embedded state, is the real spectrum of idempotence in practice.
None of that makes OpenTofu bad. But it does make the concept of idempotence something worth defining clearly, especially when security and platform reliability depend on it.
In real systems we often choose, or more often, inherit, something less than idempotently pure. Other times it simply loses against other equally important concerns: speed, simplicity, legacy constraints, or just getting something out the door. That’s fine. Understanding idempotency doesn’t mean you must pursue it everywhere. It does mean you have a lens to reason about systems that aren’t idempotent, and to fix or harden them when it matters.
In that early system, we ended up using simple Ansible plays to configure and manage Kubernetes itself. They were deliberately boring: check state, make the smallest possible change, and no-op when nothing needed doing. We could rerun the deployment at any point, during incidents or after partial failures, and trust the outcome.
That colleague was onto something. I wish I’d had a better answer at the time.
-
“Idempotent.” MathWorld—A Wolfram Web Resource, https://mathworld.wolfram.com/Idempotent.html
-
“Idempotence & Idempotent Design in IT/Tech Systems.” Splunk, https://www.splunk.com/en_us/blog/learn/idempotent-design.html
-
“Terraform drift is breaking your cloud: here’s how to stop the chaos.” Run.it.Bare (Medium), https://medium.com/devlink-tips/terraform-drift-is-breaking-your-cloud-heres-how-to-stop-the-chaos-719a7a5ef03b
-
Arslan, Fatih. “How to write idempotent Bash scripts.” arslan.io, https://arslan.io/2019/07/03/how-to-write-idempotent-bash-scripts