Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Idempotent Infrastructure: Principles and Practice

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.

Idempotent Run = Same End StateRun #1Apply configRun #2Apply againIdentical Production StateUsers, files, services unchangedNon-idempotent: duplicates, drift, broken configEach re-run adds or breaks something
Idempotent infrastructure: two provisioning runs converge on one stable production state.

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.

Idempotent Deploy PipelineGit PushSame commitCI BuildCached artefactIaC ApplyPlan then applyApp DeploySymlink swapLiveSame stateRetry any stage — outcome unchanged if inputs unchangedSecrets from vaultNot re-written each runHealth check gateAbort before bad state
An idempotent CI/CD pipeline: each stage can retry safely when designed for convergence.

How does idempotency work in Infrastructure as Code and config tools?

Different tools express idempotency differently. Know the model before you blame the tool.

ToolIdempotency modelSecond-run behaviourCommon pitfall
TerraformDeclarative graph + state filePlan shows zero changes when convergedDrift outside Terraform; manual edits
AnsibleModule check mode + changed flagTasks report ok not changedRaw shell without creates guard
CloudFormation / BicepStack desired stateUpdate with no property changesReplacement triggers on renames
Deployer / CapistranoRelease directory + symlinkNew release folder; same live pathShared tasks that mutate global config
Laravel migrationsmigrations table ledgerAlready-run batches skippedData 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.

Idempotent vs Non-Idempotent OpsIdempotentSafe CI retriesDrift self-healsPredictable rollbacksAudit via Git + stateLower on-call stressUpfront design costNon-IdempotentRetry causes duplicatesManual fixes compoundRollback is guessworkSnowflake serversHigher incident rateFast first script
Idempotent infrastructure trades short setup time for long-term operational safety.

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.

  1. Double-run in CI — Run Ansible with --check first, then for real. Immediately re-run; expect zero changes.
  2. Terraform plan gate — Fail the pipeline if terraform plan shows changes on a clean branch with no code diff.
  3. Idempotency integration tests — Spin a throwaway VM, provision twice, assert file hashes and service status match.
  4. Drift scans — Schedule read-only plans or tools like terraform plan -detailed-exitcode in cron; alert on exit code 2.
  5. 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
Idempotent Tool Choice by LayerWhat are you changing?Cloud resourcesUse Terraform / BicepOS configUse Ansible modulesApp releaseDeployer / GitOpsVerify: second run = zero changesPlan gate in CI · Ansible --check · migrate statusLog and alert on unexpected driftDocument exceptions explicitly
Choose idempotent tooling by infrastructure layer, then verify with a second no-op run.

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

Idempotent infrastructure means every provisioning, deploy, and config step can run multiple times and still produce the same desired state—no duplicate resources, no drift, and no manual cleanup afterward.

Non-idempotent scripts are a hidden tax. I've seen provisioning create duplicate admin users, append the same Nginx block twice, or re-run seeds that duplicate payment gateway rows. Idempotent design gives you safe CI retries, drift correction when scheduled applies pull reality back to declared state, and auditability where code is the contract. 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 exactly the goal.

Principles beat tools. Declare desired state in Terraform HCL, Ansible modules, or Kubernetes manifests—not imperative click sequences. Make every step check-before-act: read before creating, compare checksums before writing. Separate mutable data such as uploads and order rows from replaceable compute on persistent volumes or managed databases. Use stable resource names and tags, not random suffixes that change every apply. Design steps to be atomic where possible and resumable where not, so network blips do not leave half-applied snowflake servers.

Terraform tracks resource IDs in remote state with locking. A second terraform apply with unchanged code should report zero to add, zero to change, zero to destroy. The provider reconciles desired configuration against the remote API. Treat a non-empty plan on a clean repo as a smell—someone edited production by hand or state is stale. Common pitfalls include drift outside Terraform, manual edits, and resources named with timestamps that guarantee a new resource every apply and never let state converge.

No. Terraform aims for idempotent applies only when code, state, and reality match. Random names, lifecycle rules forcing replacement, or manual drift break convergence. A clean second plan with no changes confirms idempotency.

Ansible modules such as apt, copy, template, file, and community.general.ufw compare current state before mutating. A well-written playbook shows changed on first run and ok thereafter. Raw shell tasks opt out of that safety—use modules with explicit state parameters instead. For example, deploying an Nginx vhost via template and enabling it with a symlink both notify a handler only when something actually changed. Ansible documentation states modules should be safe to run repeatedly; check mode helps verify before the real apply.

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 and failed retries.

Laravel records each completed migration batch in the migrations table. Subsequent php artisan migrate commands skip files already in that ledger—that table is the idempotency mechanism. This is why I follow zero-downtime Laravel migration practices on booking systems. Destructive or data-changing migrations still need careful production review. For seeders, never use blind insert() in production; use firstOrCreate, upserts, or existence checks, as with Spatie Permission roles seeded via Role::firstOrCreate for admin and staff.

Append-only config edits—echoing Include site.conf into apache2.conf every deploy until Apache fails. SQL bootstrap without CREATE IF NOT EXISTS guards. Time-based or random S3 bucket and security group names that balloon Terraform state. Secrets re-written every run, breaking downstream integrations—fetch from a vault and write only when missing or explicitly rotated. Mixing imperative hotfixes with declarative IaC: someone opens port 443 manually, then the next apply closes it or fails mid-run. Pick one source of truth and enforce it with GitOps or scheduled drift detection.

Do not assume—prove. Run Ansible with check mode first, then for real, then immediately re-run expecting zero changes. Fail the pipeline if terraform plan shows changes on a clean branch with no code diff. Spin a throwaway VM, provision twice, and assert file hashes and service status match. Schedule read-only terraform plan -detailed-exitcode in cron and alert on exit code 2. After Deployer symlink swap, hit health endpoints and compare response schema. A GitLab CI pattern applies Terraform, then runs a second plan that must exit 0 with no pending changes.

Idempotent infrastructure ensures re-running configuration reaches the same end state without side effects. Immutable infrastructure achieves change by replacing whole instances, AMIs, container images, or release directories rather than patching in place. You often use both: immutable releases deployed through idempotent scripts. On high-churn web tiers, combine Packer-style golden images with idempotent provisioning of UFW rules, PHP-FPM pools, and log rotation on Ubuntu VPS hosts common on Nepal client projects.

GitOps makes Git the desired-state source; controllers reconcile continuously. That loop only works if reconciliation is idempotent—a controller creating a new Service every sync would be unusable. At the application boundary, API idempotency keys apply the same principle: payment webhooks and order creation should accept a client-supplied key so retries do not double-charge, mirroring infra retries. Pair both with least privilege IAM so a bad re-run cannot delete unrelated stacks when a non-idempotent script slips through.

Deployer and Capistrano use release directories plus a symlink swap to the live path. Each deploy creates a new release folder; the live path stays stable. That makes the deploy step idempotent at the application layer. Pitfall: shared tasks that mutate global config outside the release tree break convergence. On legal-tech sister sites I maintain with Deployer 7 and GitLab CI, idempotent release steps support rollback and re-deploy without drama. Keep hooks from append-only edits to Apache or Nginx global configs—templating or idempotent lineinfile with markers instead.

For greenfield apps, bake idempotency into architecture early via declarative IaC and guarded migrations. Retrofitting a snowflake server farm costs more than Rs 150,000 (~USD 1,100) in incident time alone—hours lost to duplicate users, doubled config blocks, manual 2 a.m. SSH fixes overwritten by the next Terraform apply, and eroded trust in automation. Idempotent infrastructure trades short setup time for long-term operational safety. Small teams feel this tax hardest because one person often owns dev, deploy, and on-call.

Choose by infrastructure layer, then verify with a second no-op run. Terraform excels at declarative cloud resource graphs with state-backed convergence—S3 buckets, security groups, load balancers with stable names per environment. Ansible excels at idempotent server baseline on bare-metal or VPS: UFW rules, Nginx vhosts, PHP-FPM pools, log rotation on Ubuntu before app deploy. CloudFormation and Bicep follow stack desired-state models similar to Terraform. Neither replaces the other; Terraform declares cloud shape, Ansible converges OS config, and application migrations handle schema via Laravel's migrations table ledger.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: