
September 12, 2026
12 min read
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.
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:
- Safe retries: CI pipelines can re-run failed stages without fear. GitLab CI, GitHub Actions, and Jenkins all assume steps are safe to repeat.
- Drift correction: Scheduled Ansible or GitOps reconciliation pulls reality back toward code.
- Audit confidence: "We ran the playbook" means the server matches the repo. No hidden manual steps.
- 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
| Tool | Idempotency model | State tracking | Common pitfall |
|---|---|---|---|
| Ansible | Module checks before change | None by default (ad hoc) | command / shell modules always run |
| Terraform | Plan diff against state file | Remote or local state | Provisioners bypass drift detection |
| Deployer | Task guards + symlinks | Release directories | Shared files overwritten without checks |
| Kubernetes controllers | Reconciliation loop | etcd cluster state | Imperative kubectl edits fight GitOps |
| Shell scripts | Manual — not guaranteed | None | useradd fails on second run |
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
createsorunlessguards in shell tasks when Ansible modules do not fit. - Prefer
IF NOT EXISTSin 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.
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.
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.
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
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.

