
September 11, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Your second deploy should not be scarier than your first. Idempotent Infrastructure: Principles and Practice is the discipline of designing servers, pipelines, and provisioning so repeated runs converge on the same end state without side effects. A manual SSH fix at 2 a.m. feels fast until the next Terraform apply overwrites it or a cron job doubles a charge. This guide covers the principles, the tooling patterns, and the production checks I use on real systems.
What is idempotent infrastructure and why does it matter?
Idempotency comes from mathematics: apply an operation twice and the result equals applying it once. In infrastructure, that means running your deploy playbook, migration job, or IaC plan twice leaves production in the same shape.
Non-idempotent scripts are the hidden tax on small teams. I've seen a provisioning script create a second admin user, append the same Nginx block twice, or re-run a seed that duplicates payment gateway rows. Each failure wastes an hour and erodes trust in automation.
Idempotent design supports three outcomes you actually care about:
- Safe retries — CI/CD can re-run failed stages without fear.
- Drift correction — scheduled applies pull reality back to declared state.
- Auditability — the code is the contract; logs show convergence, not mystery edits.
On sister legal-tech sites I maintain with Deployer 7 and GitLab CI, idempotent release steps are what make rollback and re-deploy boring—which is the goal.
How do you apply the core principles of idempotent infrastructure?
Principles beat tools. The stack changes; these rules stay useful across Terraform, Ansible, shell, and application deploy scripts.
Declare desired state, not imperative steps
Write what should exist, not a sequence of clicks. Terraform HCL, Ansible modules, and Kubernetes manifests all express target state. The engine calculates the delta. Imperative bash that runs useradd without checking first will fail on the second run.
Make every step check-before-act
Before creating, read. Before writing, compare checksums or hashes. Package managers, migration frameworks, and object stores all support this pattern when you use them correctly.
Separate mutable data from replaceable compute
Idempotency applies to configuration and code paths. User uploads, order rows, and audit logs live on persistent volumes or managed databases—not inside golden images you rebuild hourly. See immutable infrastructure with Packer for the compute side; keep data out of the blast radius.
Version and name resources predictably
Random suffixes on every apply destroy idempotency across environments. Use stable names, tags, and remote state locks so a second run addresses the same logical resource.
Design for partial failure and retry
Network blips happen. Steps should be atomic where possible and resumable where not. Queue workers, Terraform state locking, and database transactions each solve part of this puzzle.
How does idempotency work in Infrastructure as Code and config tools?
Different tools express idempotency differently. Know the model before you blame the tool.
| Tool | Idempotency model | Second-run behaviour | Common pitfall |
|---|---|---|---|
| Terraform | Declarative graph + state file | Plan shows zero changes when converged | Drift outside Terraform; manual edits |
| Ansible | Module check mode + changed flag | Tasks report ok not changed | Raw shell without creates guard |
| CloudFormation / Bicep | Stack desired state | Update with no property changes | Replacement triggers on renames |
| Deployer / Capistrano | Release directory + symlink | New release folder; same live path | Shared tasks that mutate global config |
| Laravel migrations | migrations table ledger | Already-run batches skipped | Data seeds without guards in production |
Terraform: plan twice, apply once
Terraform tracks resource IDs in state. A second terraform apply with unchanged code should report no changes. That is your idempotency proof. Use remote state, locking, and modules from reusable Terraform modules so environments stay predictable.
# main.tf — idempotent S3 bucket with stable name
resource "aws_s3_bucket" "app_assets" {
bucket = "myapp-prod-assets-kokil"
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
# Second apply with same code: 0 to add, 0 to change, 0 to destroy
Official Terraform docs describe how providers reconcile desired configuration with remote APIs. Treat a non-empty plan on a clean repo as a smell—someone edited production by hand or state is stale.
Ansible: prefer modules over shell
Ansible modules are idempotent by design. The apt, copy, and template modules compare before they mutate. Raw shell is opt-out of safety.
# tasks/nginx.yml — idempotent site enable
- name: Deploy Nginx vhost
template:
src: laravel.conf.j2
dest: /etc/nginx/sites-available/myapp.conf
owner: root
group: root
mode: '0644'
notify: reload nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/myapp.conf
dest: /etc/nginx/sites-enabled/myapp.conf
state: link
notify: reload nginx
The Ansible documentation states that modules should be safe to run repeatedly. If your playbook only shows changed on first run and ok thereafter, you are doing it right.
Application layer: migrations and seeds
Laravel migrations are idempotent via the migrations table. Running php artisan migrate twice skips completed files. That pattern is why I follow zero-downtime Laravel migration practices on booking systems like Adventure Third Pole Trek.
// database/seeders/RolesSeeder.php — idempotent seed
use Spatie\Permission\Models\Role;
public function run(): void
{
Role::firstOrCreate(['name' => 'admin']);
Role::firstOrCreate(['name' => 'staff']);
}
Never use blind insert() in production seeders. Use firstOrCreate, upserts, or existence checks. Read database migrations and seeding best practices for the full picture.
What are common idempotency failures in production?
Knowing the theory does not stop the 3 a.m. page. These failures show up repeatedly on client servers and in CI logs.
Append-only config edits
A deploy hook runs echo "Include site.conf" >> apache2.conf every release. After ten deploys Apache fails to start. Fix: templated config or idempotent lineinfile with a marker.
CREATE without IF NOT EXISTS
SQL bootstrap scripts that lack guards break the second run. Database migrations exist precisely to avoid this. For ad-hoc SQL, use CREATE TABLE IF NOT EXISTS or migration files.
Time-based or random resource names
Creating S3 buckets or security groups with ${timestamp()} guarantees a new resource every apply. Terraform then never converges; state balloons. Use stable identifiers per environment.
Secrets re-written on every run
Rotating API keys each deploy breaks downstream integrations. Fetch secrets from a vault; write only when missing or explicitly rotated. See CI/CD secrets management best practices.
Mixing imperative hotfixes with declarative IaC
Someone opens port 443 manually. The next Terraform apply closes it. Or worse—the apply fails mid-run. Pick a source of truth and enforce it with GitOps principles or scheduled drift detection.
How do you test and verify infrastructure idempotency?
Do not assume—prove. Build verification into CI the same way you build unit tests into application code.
- Double-run in CI — Run Ansible with
--checkfirst, then for real. Immediately re-run; expect zero changes. - Terraform plan gate — Fail the pipeline if
terraform planshows changes on a clean branch with no code diff. - Idempotency integration tests — Spin a throwaway VM, provision twice, assert file hashes and service status match.
- Drift scans — Schedule read-only plans or tools like
terraform plan -detailed-exitcodein cron; alert on exit code 2. - Post-deploy smoke tests — Hit health endpoints after symlink swap; compare response schema with a stored fixture in your JSON formatter workflow during dev.
Example GitLab CI idempotency check
# .gitlab-ci.yml excerpt
terraform_plan:
stage: validate
script:
- terraform init -input=false
- terraform plan -detailed-exitcode -out=tfplan
allow_failure:
exit_codes: [2]
terraform_apply:
stage: deploy
script:
- terraform apply -auto-approve tfplan
- terraform plan -detailed-exitcode
# Second plan must exit 0 (no changes)
Pair this with zero-downtime Terraform updates when you change load balancers or databases. Idempotency does not remove the need for careful ordering on stateful resources.
Server baseline on Ubuntu
For bare-metal or VPS work—common on Nepal client projects—I script baseline hardening idempotently before app deploy. UFW rules, PHP-FPM pools, and log rotation should survive re-runs. Cross-check with Ubuntu server security best practices and professional Linux system administration when the server is business-critical.
# Idempotent UFW allow — Ansible
- name: Allow HTTPS
community.general.ufw:
rule: allow
port: '443'
proto: tcp
How do idempotency and related patterns fit together?
Idempotent infrastructure pairs with—but is not the same as—several neighbouring ideas.
Immutable infrastructure replaces instances instead of patching them. New AMI, new container image, new release directory. The deploy step is idempotent; old nodes are discarded. Combine both on high-churn web tiers.
GitOps makes Git the desired-state source. Controllers reconcile continuously. That loop only works if reconciliation is idempotent. A controller that creates a new Service every sync would be unusable.
API idempotency keys apply the same principle at the application boundary. Payment webhooks and order creation should accept a client-supplied key so retries do not double-charge. That mirrors infra retries and belongs in REST API design best practices for platforms like Notary Nepal.
Least privilege IAM limits blast radius when a non-idempotent script runs anyway. Tight roles mean a bad re-run cannot delete unrelated stacks. Review AWS IAM least privilege practices alongside your IaC roles.
For greenfield apps, bake idempotency into architecture early via enterprise application development and modern Laravel architecture. Retrofitting a snowflake server farm costs more than Rs 150,000 (~USD 1,100) in incident time alone.
Key Takeaways
- Idempotent infrastructure means repeated runs converge on the same state—no duplicates, no compounding edits.
- Prefer declarative IaC and Ansible modules over append-only shell scripts.
- Prove idempotency in CI with double-run checks and Terraform plan gates.
- Keep persistent data off replaceable compute; migrations and seeds need explicit guards.
- Stable resource names and secret handling prevent state drift and broken retries.
- Pair idempotent deploys with GitOps, immutable releases, and API idempotency keys for end-to-end safety.
People Also Ask
What is the difference between idempotent and immutable infrastructure?
Idempotent infrastructure ensures re-running configuration reaches the same state. Immutable infrastructure achieves change by replacing whole instances or images rather than patching in place. You often use both: immutable releases deployed through idempotent scripts.
Is Terraform always idempotent?
Terraform aims for idempotent applies when code and state match reality. It fails when resources use random names, when lifecycle rules force replacement, or when manual changes create drift. A clean second plan with no changes confirms idempotency.
Can bash scripts be idempotent?
Yes, if every action checks current state first—test file existence, compare hashes, use CREATE IF NOT EXISTS. Without guards, bash is the fastest path to non-idempotent ops. Reach for Ansible modules or small idempotent wrappers instead.
Why do Laravel migrations run only once?
Laravel records each migration batch in the migrations table. Subsequent migrate commands skip completed files. That ledger is the idempotency mechanism; destructive or data-changing migrations still need careful review for production.
Ship infrastructure you can run twice
Idempotent Infrastructure: Principles and Practice is not academic—it is how you sleep through deploy night. Declare desired state, verify with a second no-op run, and stop patching snowflakes by hand. If your pipeline still feels fragile, I help teams harden Linux hosts, CI/CD, and Laravel releases through support and maintenance and custom automation work. Contact us to review your deploy path, or browse the portfolio for production examples.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

