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.

Idempotency in Infrastructure Automation

By Kokil Thapa | Last reviewed: September 2026

Idempotency in infrastructure automation is the property that lets you re-run the same script, playbook, or Terraform plan without breaking production. A second run should leave the server, network, and application in the same desired state. It should not create duplicate users, restart services unnecessarily, or double-charge a payment webhook. On real client projects I maintain with Deployer 7 and GitLab CI, idempotent deploy steps are what separate a safe rollback from a midnight outage. This guide covers the theory, the tooling, and the checks you can apply today.

What Is Idempotency in Infrastructure Automation?

Idempotency comes from mathematics. An operation is idempotent when applying it twice equals applying it once. In infrastructure, that translates to concrete outcomes. Creating a firewall rule that already exists should report "unchanged." Installing PHP 8.3 on a server that already runs 8.3 should skip the package manager. Running your deploy pipeline twice after a successful release should not corrupt symlinks or duplicate cron entries.

The concept sits at the heart of infrastructure as code (IaC). Without idempotency, automation becomes a loaded gun. Teams hesitate to re-run failed jobs. Manual fixes drift from code. Incidents linger because nobody trusts the pipeline.

Idempotency differs from related ideas you will see in ops discussions:

  • Declarative vs imperative: Declarative tools describe what should exist. Imperative scripts describe how to get there. Declarative models encourage idempotency; imperative scripts require discipline. See declarative vs imperative infrastructure for the full comparison.
  • Immutable vs mutable: Immutable infrastructure replaces entire servers rather than patching them. That sidesteps some idempotency problems but shifts them to image builds. Immutable vs mutable infrastructure explains when each model fits.
  • Configuration management idempotency: The same principle applies at the OS layer. Our companion article on idempotency in configuration management goes deeper on Ansible and Chef specifics.
Idempotency in Infrastructure AutomationRun 1Apply playbookRun 2Same playbookRun NSafe retrySame Desired End StateNo duplicates, no drift, no extra restartsFoundation for safe CI/CD retries, GitOps reconciliation, and disaster recovery
Idempotency in infrastructure automation: repeated runs converge on one desired state instead of stacking changes.

Why Does Idempotency Matter for Production Infrastructure?

Production systems fail mid-run. Network blips interrupt package downloads. SSH sessions drop during deploys. Terraform state locks expire. Without idempotent automation, your only option is manual cleanup. That cleanup is slow, error-prone, and rarely documented.

I have seen this on shared EC2 hosts running multiple Laravel sites. A non-idempotent deploy script appended the same cron line three times. Scheduled tasks ran triple, queue workers competed, and disk I/O spiked. The fix took longer than writing idempotent deploy tasks in the first place.

Idempotent automation delivers four practical wins:

  1. Safe retries: CI pipelines can re-run failed stages without fear. GitLab CI, GitHub Actions, and Jenkins all assume steps are safe to repeat.
  2. Drift correction: Scheduled Ansible or GitOps reconciliation pulls reality back toward code.
  3. Audit confidence: "We ran the playbook" means the server matches the repo. No hidden manual steps.
  4. Faster onboarding: New engineers run the same automation veterans use. Results are predictable.

For teams in Nepal running lean ops, this matters. You often lack a dedicated platform team. One senior developer handles code, server, DNS, and backups. Idempotent scripts reduce the blast radius when that person is unavailable during Dashain or Tihar.

How Do Ansible and Terraform Handle Idempotency?

Different tools implement idempotency at different layers. Understanding each tool's contract prevents false confidence.

Ansible modules

Ansible modules are designed to be idempotent by default. The apt, file, user, and template modules check current state before acting. A task reports ok when nothing changed and changed when it modified the system. Official Ansible documentation describes this as the core playbook behaviour model.

# roles/php/tasks/main.yml — idempotent PHP-FPM install
- name: Install PHP 8.3 and extensions
  ansible.builtin.apt:
    name:
      - php8.3-fpm
      - php8.3-mysql
      - php8.3-redis
    state: present
    update_cache: true
  become: true

- name: Ensure PHP-FPM is running
  ansible.builtin.service:
    name: php8.3-fpm
    state: started
    enabled: true
  become: true

- name: Deploy pool config only if template changed
  ansible.builtin.template:
    src: www.conf.j2
    dest: /etc/php/8.3/fpm/pool.d/www.conf
    validate: php-fpm8.3 -t
  notify: Reload php-fpm
  become: true

Handlers restart services only when notified. That avoids unnecessary PHP-FPM reloads on every playbook run. Use Ansible roles to package these patterns for reuse across servers.

Terraform resources

Terraform tracks resource state in a state file. On apply, it compares desired configuration with actual cloud/API state. Unchanged resources show No changes. The HashiCorp resource behaviour docs explain how refresh and plan interact.

# main.tf — idempotent AWS security group rule
resource "aws_security_group" "web" {
  name        = "laravel-web-sg"
  description = "HTTP and HTTPS for Laravel app"
  vpc_id      = var.vpc_id
}

resource "aws_vulnerability_security_group_rule" "https" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.web.id
}

Some Terraform resources are inherently non-idempotent. null_resource with local-exec provisioners runs commands every time unless you add triggers carefully. Prefer cloud-native resources or external data sources. Our Terraform practical guide covers module patterns that keep applies predictable.

Comparison table

ToolIdempotency modelState trackingCommon pitfall
AnsibleModule checks before changeNone by default (ad hoc)command / shell modules always run
TerraformPlan diff against state fileRemote or local stateProvisioners bypass drift detection
DeployerTask guards + symlinksRelease directoriesShared files overwritten without checks
Kubernetes controllersReconciliation loopetcd cluster stateImperative kubectl edits fight GitOps
Shell scriptsManual — not guaranteedNoneuseradd fails on second run
Tool Idempotency FlowCurrent StateAutomationAnsible / TerraformDesired StateDiff: change only what differsok / unchangedSecond run safechangedConverge toward goalfailedFix and retry safely
Ansible and Terraform idempotency: compare current state to desired state, then act only on the diff.

How Do You Write Idempotent Deploy and CI/CD Steps?

Application deploy automation sits between configuration management and release engineering. Even if Ansible configured the server correctly, a bad deploy script can still break idempotency.

Deployer 7 pattern

On sister legal-tech sites I maintain, Deployer 7 uses symlinked releases. The deploy task creates a new release directory, runs Composer, swaps the current symlink, and reloads PHP-FPM. Shared directories (storage, .env) persist across releases.

# deploy.php — idempotent deploy tasks
namespace Deployer;

require 'recipe/laravel.php';

set('keep_releases', 5);
set('shared_dirs', ['storage']);
set('shared_files', ['.env']);

task('deploy:vendors', function () {
    run('cd {{release_path}} && {{bin/composer}} install --no-dev --prefer-dist -o');
});

after('deploy:symlink', 'php-fpm:reload');

task('php-fpm:reload', function () {
    run('sudo systemctl reload php8.3-fpm');
});

Re-running a successful deploy on the same commit should fail early or no-op. Deployer tracks release paths. The symlink swap is atomic. That is idempotent at the release level. Pair this with build pipeline best practices so artefacts are immutable.

GitLab CI guards

CI jobs should declare what they expect before mutating infrastructure:

# .gitlab-ci.yml excerpt
deploy_production:
  stage: deploy
  script:
    - dep deploy production -o strict_host_key_checking=no
  environment:
    name: production
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
  retry:
    max: 2
    when: runner_system_failure

Retries on runner failure are safe only when deploy tasks are idempotent. Never retry blindly on application errors without checking partial state. Use JSON formatters to inspect webhook payloads when debugging idempotent API callbacks alongside infra work.

Checklist for idempotent scripts

  • Use creates or unless guards in shell tasks when Ansible modules do not fit.
  • Prefer IF NOT EXISTS in SQL migrations for indexes and columns where your framework supports it.
  • Make file writes atomic: write to temp path, then rename.
  • Store external resource IDs in state files or parameter stores.
  • Log whether each step changed, skipped, or failed.
Idempotent Deploy PipelineGit PushGitLab CIDeployerNew releaseSymlinkAtomic swapLiveRetry Safe ZoneRe-run creates new release or no-ops; shared storage untouchedBad: append cronDuplicates on retryGood: template cronOverwrite same file
Idempotent deploy pipeline: GitLab CI plus Deployer symlink releases allow safe retries without duplicate side effects.

What Breaks Idempotency in Real Production Systems?

Knowing how idempotency fails is as important as knowing how to implement it. These patterns show up repeatedly across Laravel, WordPress, and mixed Linux stacks.

Imperative shell in IaC wrappers

Wrapping curl | bash installers inside Terraform local-exec or Ansible shell tasks breaks the model. Each apply re-downloads and re-runs. Pin versions. Use package modules instead.

Time-based or random resources

Generating passwords with random_password without lifecycle rules rotates secrets every plan. That triggers cascading updates. Store secrets in vaults. Reference them by key.

API rate limits and partial applies

Cloud APIs may succeed on create but fail on tag assignment. Terraform records partial state. A retry might attempt recreate. Use lifecycle { prevent_destroy = true } on critical resources. Enable state locking with S3 and DynamoDB or GitLab managed Terraform state.

Mutable manual changes

An admin edits Nginx config by hand. The next Ansible run overwrites it—or worse, skips because a naive stat check passes on the wrong file. Enforce Linux system administration discipline: production changes flow through code.

Non-idempotent application hooks

Payment webhooks and provisioning callbacks must use idempotency keys, not just infra scripts. The same logical operation—create subscription, charge card—needs a stable client token. See our API idempotency keys guide for application-layer patterns on Laravel APIs.

On a production Laravel application, I treat deploy scripts and payment webhooks with the same rule: if you cannot run it twice safely, it is not finished.

Projects like Adventure Third Pole Trek combine booking logic with scheduled infra tasks. A duplicate cron entry there could double-charge supplier notifications. Idempotency spans code and servers.

Idempotency Decision TreeNeed to change infra?Cloud resourcesOS configApp deployUse TerraformPlan before applyUse AnsibleModule not shellSymlink deployShared storageAvoid: raw shell without guards
Decision tree for idempotency in infrastructure automation: match the tool to the change type.

How Do You Test and Verify Idempotent Infrastructure Code?

Idempotency is a behaviour, not a checkbox. You verify it by running automation twice and inspecting outcomes.

Ansible idempotence check

ansible-playbook site.yml --check  # dry run first pass
ansible-playbook site.yml          # first real run
ansible-playbook site.yml          # second run should show 0 changed

Molecule integrates this into CI. For ad hoc verification, parse the recap line. Anything other than changed=0 on the second run deserves investigation.

Terraform plan stability

terraform plan -out=tfplan
terraform apply tfplan
terraform plan   # expect: No changes. Your infrastructure matches the configuration.

Drift from manual edits will appear on the second plan. That is working as designed. Import or fix drift, then re-plan. Terratest automates these assertions in Go for larger teams.

Integration with Kubernetes

Kubernetes controllers reconcile desired manifests continuously. The Kubernetes object model treats the manifest as source of truth. Repeated kubectl apply calls are idempotent at the API level. Field ownership conflicts arise when mixed imperative edits fight declarative files—keep GitOps single-sourced.

Schedule regular convergence runs. Weekly Ansible against production catches drift before it becomes an incident. Pair automated checks with testing and optimization reviews after major upgrades.

Two-Pass Idempotency TestPass 1: ApplyExpect changed > 0 on fresh hostPass 2: Re-applyExpect changed = 0Assert: state identical, services healthyLogs, metrics, smoke tests pass both timesPassShip with confidenceFailFix non-idempotent taskCI gateBlock merge on fail
Two-pass verification proves idempotency in infrastructure automation before production promotion.

For broader automation context, read idempotent infrastructure principles, zero-downtime Terraform updates, and Python for DevOps automation. Teams building custom platforms benefit from enterprise application development practices that treat ops code as first-class.

Hosting costs for idempotent pipelines are modest. A GitLab runner on a Rs 3,500/month VPS (~USD 26) can test Ansible and deploy Laravel apps reliably. The expensive part is downtime when idempotency fails—not the tooling.

Key Takeaways

  • Idempotency in infrastructure automation means repeated runs converge on the same desired state without duplicate resources or harmful side effects.
  • Prefer declarative modules—Ansible apt, Terraform resources, Deployer symlink releases—over raw shell commands.
  • Always run a second pass in CI or staging; expect zero changes on the re-run before promoting to production.
  • Guard application webhooks and provisioning APIs with idempotency keys, not just server playbooks.
  • Store state centrally (Terraform remote state, GitOps repos) so automation knows what already exists.
  • Schedule periodic convergence to catch manual drift before it becomes a production incident.

People Also Ask

Is Terraform always idempotent?

Not automatically. Core resources are idempotent through state comparison, but provisioners, some data sources, and misconfigured lifecycle blocks can cause repeated changes. Always run terraform plan twice on a clean environment during development.

What is the difference between idempotent and immutable infrastructure?

Idempotent systems update existing resources toward a desired state. Immutable systems replace entire instances with new images. Immutable deploys reduce patch-level idempotency concerns but require idempotent image builds. Many teams combine both: golden AMI images plus idempotent Ansible bootstrap.

Can bash scripts be idempotent?

Yes, with explicit guards: check file existence before create, use useradd only when id fails, write configs via temp files and atomic rename. Without guards, bash scripts are imperative and risky to retry. Wrap them in Ansible or migrate logic to proper modules.

Why do CI pipelines assume idempotency?

Runners fail, networks timeout, and platforms retry jobs automatically. If a deploy step is not idempotent, a transient failure becomes data corruption. CI vendors design around repeatable steps; your scripts must match that contract.

Build Automation You Can Re-Run Without Fear

Idempotency in infrastructure automation is not academic—it is what lets you sleep during deploy night. Start by auditing shell tasks in your playbooks. Replace them with modules. Run the two-pass test on staging. Wire the check into CI. If you want help hardening Deployer pipelines, Terraform modules, or Laravel hosting on Ubuntu, review our notary site portfolio work and client reviews, then contact us for a practical infrastructure review.

Frequently Asked Questions

Idempotency means applying the same script, playbook, or Terraform plan repeatedly reaches the same desired end state without unintended side effects. Running it twice equals running it once: existing firewall rules stay unchanged, PHP 8.3 is not reinstalled, and deploy pipelines do not duplicate cron entries or corrupt symlinks.

Production jobs fail mid-run from network blips, dropped SSH sessions, or expired Terraform state locks. Without idempotent automation, recovery means slow manual cleanup that drifts from code. Idempotent pipelines enable safe CI retries, scheduled drift correction, audit confidence that servers match the repo, and predictable onboarding. On lean Nepal ops teams where one developer handles code, servers, and DNS, idempotent scripts reduce blast radius when that person is unavailable during Dashain or Tihar.

Ansible modules such as apt, file, user, and template check current state before acting. Tasks report ok when nothing changed and changed when they modified the system. Handlers restart services like PHP-FPM only when notified, avoiding unnecessary reloads on every playbook run. The command and shell modules are the common pitfall—they always run unless you add creates or unless guards manually.

Not automatically. Core resources compare desired configuration against state and show No changes when aligned, but null_resource with local-exec provisioners, some data sources, and misconfigured lifecycle blocks can cause repeated changes on every apply.

Idempotent systems update existing resources toward a desired state—Ansible installs packages, Terraform adjusts security group rules, Deployer swaps symlinks. Immutable infrastructure replaces entire server instances rather than patching them in place. That sidesteps some idempotency problems at the OS layer but shifts them to image builds and deployment orchestration instead.

Deployer 7 uses symlinked releases on servers I maintain for sister legal-tech sites. Each deploy creates a new release directory, runs Composer install, atomically swaps the current symlink, and reloads PHP-FPM. Shared directories like storage and shared files like .env persist across releases. Re-running a successful deploy on the same commit should fail early or no-op because Deployer tracks release paths and the symlink swap is atomic at the release level.

Retries are safe only when deploy tasks are idempotent. GitLab CI can retry up to twice on runner_system_failure, but you should never retry blindly on application errors without checking partial state first. The pipeline assumes steps like dep deploy production can be re-run without duplicating side effects. Pair manual deploy rules on the main branch with Deployer symlink releases so a failed stage does not leave corrupted symlinks or partial Composer installs.

Common failures include imperative curl pipe bash inside Terraform local-exec or Ansible shell tasks, random_password resources without lifecycle rules that rotate secrets every plan, cloud API partial applies that record incomplete state, manual Nginx edits that drift from code, and non-idempotent application hooks like payment webhooks without idempotency keys. On shared EC2 hosts running multiple Laravel sites, a non-idempotent deploy script once appended the same cron line three times, tripling scheduled tasks and spiking disk I/O.

Run ansible-playbook site.yml with --check for a dry run, then execute it twice on a real target. The second run should show changed=0 in the recap line. Anything other than zero changed deserves investigation. Molecule integrates this two-pass check into CI. Schedule weekly Ansible convergence against production to catch manual drift before it becomes an incident, especially after major upgrades.

Run terraform plan -out=tfplan, apply the saved plan, then run terraform plan again. The second plan should report No changes. Your infrastructure matches the configuration. Drift from manual edits will appear on that second plan, which is working as designed—import or fix the drift, then re-plan. Terratest automates these assertions for larger teams. Enable state locking with S3 and DynamoDB or GitLab managed Terraform state to prevent concurrent applies corrupting idempotency.

Declarative tools like Ansible modules and Terraform resources describe what should exist and compare current state to desired state before acting. Imperative scripts describe how to get there step by step and require manual discipline to stay idempotent. Wrapping raw shell commands in IaC wrappers breaks the model because each apply re-downloads and re-runs without checking whether the outcome already exists, which is why useradd fails on a second run in naive scripts.

Modest—a GitLab runner on a Rs 3,500/month VPS (~USD 26) can test Ansible and deploy Laravel apps reliably. Downtime from failed idempotency costs far more than the tooling itself.

Kubernetes controllers reconcile desired manifests continuously against etcd cluster state. Repeated kubectl apply calls are idempotent at the API level because the manifest is the source of truth. GitOps keeps that single source in a repo. Field ownership conflicts arise when imperative kubectl edits fight declarative files, so keep GitOps single-sourced. Imperative edits bypass the reconciliation loop and reintroduce the same drift problems Ansible and Terraform are designed to prevent.

Idempotency spans application code and servers, not just playbooks. Payment webhooks and provisioning callbacks must use idempotency keys so the same logical operation—create subscription, charge card—does not execute twice if the provider retries. On a production Laravel application, I treat deploy scripts and payment webhooks with the same rule: if you cannot run it twice safely, it is not finished. Projects combining booking logic with scheduled infra tasks risk duplicate cron entries doubling supplier notifications.

Use creates or unless guards in shell tasks when Ansible modules do not fit. Prefer IF NOT EXISTS in SQL migrations for indexes and columns where your framework supports it. Make file writes atomic by writing to a temp path then renaming. Store external resource IDs in state files or parameter stores. Log whether each step changed, skipped, or failed. Prefer declarative modules over raw shell, run a second pass in CI or staging expecting zero changes, and guard provisioning APIs with idempotency keys—not just server playbooks.

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: