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.

Declarative vs Imperative Infrastructure

By Kokil Thapa | Last reviewed: September 2026

Your server works in staging but breaks in production because nobody documented the manual steps. That gap is exactly where Declarative vs Imperative Infrastructure stops being academic and starts costing you sleep. Both models automate provisioning, but they answer different questions. Declarative code describes the end state you want. Imperative code lists the commands to run. On real client projects I maintain with infrastructure as code (IaC), the choice between these models affects drift, rollbacks, and who can safely change production.

What Is the Difference Between Declarative and Imperative Infrastructure?

Think of declarative infrastructure as a recipe card that says “serve four bowls of soup.” Imperative infrastructure is the cook shouting “chop onions, boil water, stir for ten minutes.” Both feed people. Only one survives when the cook changes mid-shift.

Declarative systems store what should exist: three app servers, one load balancer, TLS on port 443. The tool compares live reality against that definition and plans changes. Imperative systems store how to change things: run `apt upgrade`, copy this file, restart PHP-FPM. Order matters. Skipping a step breaks the chain.

Two Infrastructure ParadigmsDeclarativeDesired state filePlan → Apply → Drift detectTerraform, Bicep, CFNImperativeCommand sequenceStep 1 → Step 2 → Step 3Ansible, Bash, DeployerShared Goal: Repeatable Production EnvironmentSame Ubuntu 24 host, PHP 8.4, MySQL 8.4, TLS certDifferent mental model, different failure modes
Declarative vs imperative infrastructure: same production target, opposite control flows

The distinction matters because infrastructure failures rarely come from bad intentions. They come from untracked manual edits, wrong command order, or a deploy script that assumes yesterday’s disk layout. Understanding both models helps you pick the right default for cloud resources versus application releases.

Core vocabulary you will hear in every review

  • Desired state: the target configuration your code declares.
  • Drift: when live infrastructure no longer matches declared state.
  • Idempotency: running the same operation twice produces the same result.
  • Reconciliation: the tool’s loop that closes the gap between actual and desired.
  • Mutable vs immutable: patching servers in place versus replacing them from golden images.

For background on keeping state stable over time, read about idempotent infrastructure principles. Idempotency is the property both paradigms chase, but declarative tools enforce it at the plan layer.

How Does Declarative Infrastructure Work in Practice?

Declarative tools treat your repo as the source of truth. You commit a Terraform module, open a pull request, review the plan output, then apply. The plan shows creates, updates, and destroys before anything touches production. That review step is the safety rail small teams often skip when they rely on imperative scripts alone.

On shared EC2 infrastructure I maintain for legal-tech sister sites, Terraform-style declarative modules define VPC rules, security groups, and DNS records. Application code still deploys through GitLab CI and Deployer, which is imperative at the release layer. The split is intentional. Cloud topology changes rarely. Application code changes daily.

Example: declarative Nginx + TLS on Ubuntu

A minimal Terraform-style declaration might look like this:

resource "aws_instance" "app" {
  ami           = "ami-ubuntu-2404-lts"
  instance_type = "t3.small"

  tags = {
    Name        = "laravel-app-prod"
    Environment = "production"
  }
}

resource "aws_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
}

You never SSH in to “create a security group rule.” Terraform computes the diff. If someone adds port 22 manually, the next plan flags drift. That feedback loop is why declarative IaC pairs well with GitOps-style review workflows even outside Kubernetes.

For a deeper walkthrough, see the guide on Terraform infrastructure as code and AWS CloudFormation on AWS. Official Terraform docs at developer.hashicorp.com/terraform/docs remain the canonical reference for resource syntax and state backends.

Where declarative models shine

  1. Cloud networking, IAM, databases, and load balancers with clear resource APIs.
  2. Multi-environment parity where staging must mirror production topology.
  3. Compliance audits that need a readable desired-state document.
  4. Teams that want plan/apply gates in CI before any change lands.

Declarative code also supports modules. Reusable blocks reduce copy-paste errors across client projects. That pattern mirrors how I reuse Deployer recipes, but at the cloud layer instead of the app layer.

How Does Imperative Infrastructure Work in Practice?

Imperative automation runs commands. Ansible playbooks, Bash deploy scripts, Capistrano, and Deployer 7 all fit this bucket. You define tasks in sequence: install packages, clone Git, run Composer, symlink the release, reload PHP-FPM. Each step assumes the previous one succeeded.

I use imperative deploys daily on Laravel applications. Deployer 7 performs zero-downtime symlink swaps on Ubuntu with Apache and PHP-FPM 8.4. The script is imperative by design. It must run steps in order. You cannot declare “the site is deployed” and expect Deployer to infer Git fetch semantics.

Example: imperative Laravel deploy task

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

task('deploy:symlink', function () {
    run('ln -sfn {{release_path}} {{deploy_path}}/current');
    run('sudo systemctl reload php8.4-fpm');
});

That reload step matters. Opcache keeps old bytecode until PHP-FPM restarts or reloads. A declarative “package installed” flag would miss the runtime effect. Imperative scripts express operational reality directly.

Configuration management tools like Ansible blend both worlds. Playbooks list tasks imperatively, but modules aim for idempotent side effects. The Chef automation guide covers a similar model for mutable servers. Ansible’s official documentation at docs.ansible.com explains module idempotency flags that prevent double-install damage.

Imperative Deploy PipelineGit PushCI triggerCloneNew release dirComposer--no-dev installSymlinkZero downtimeReload FPMOpcache flushFailure point if order breaksSymlink before Composer = broken autoload
Imperative infrastructure deploy flow: ordered steps with hard dependencies between them

Imperative workflows excel when the operation is procedural. Database migrations, cache warmers, and payment-gateway webhook re-registration do not map cleanly to static resource blocks. You run them once, in order, with logging.

Which Model Should You Choose: Declarative or Imperative?

Neither side wins every fight. Mature teams usually declare cloud foundations and imperative application releases. Trying to declare every Composer post-install hook in Terraform creates YAML soup. Running all cloud networking from Bash creates undebuggable snowflake servers.

CriteriaDeclarative (Terraform, CloudFormation, Bicep)Imperative (Ansible, Bash, Deployer, CI scripts)
Primary questionWhat should exist?What commands should run?
Drift detectionBuilt into plan/apply cycleManual unless you add checks
Learning curveSteeper upfront (state, providers)Lower if you already know shell
Best fitCloud resources, DNS, IAM, networksApp deploys, migrations, one-off fixes
Rollback storyRevert commit + apply (or state rollback)Run previous script version or symlink back
IdempotencyEngine enforces at resource levelYou enforce per task/module
Team size fitStrong for multi-env teams with review gatesStrong for solo devs and small agencies

Verdict: start declarative for anything that touches billing—VPCs, RDS instances, S3 buckets, IAM roles. Keep imperative scripts for application lifecycle tasks you already understand. On a booking platform like Adventure Third Pole Trek, Laravel + Livewire code ships through imperative CI while DNS and TLS stay declarative.

If you manage servers for clients without a dedicated platform team, Linux system administration often blends both: declarative firewall rules, imperative package pinning for PHP multi-version hosts.

Decision checklist before you standardise

  • Does the API expose resources with stable IDs? Lean declarative.
  • Is the task a one-time migration or data backfill? Lean imperative.
  • Do auditors need a human-readable desired-state file? Lean declarative.
  • Does the step require live service reload semantics? Lean imperative.
  • Will non-engineers run the change? Prefer declarative plans with guardrails.
Choose Your Default ModelWhat are you changing?Cloud resourceVPC, RDS, LBApp releaseCode, assets, cacheOne-off fixMigration, hotfixDeclarativeTerraform / CFNImperativeDeployer / CIImperativeScript + audit logHybrid is normal: declare the platform, script the app
Decision tree for declarative vs imperative infrastructure by change type

What Are Common Mistakes When Mixing Declarative and Imperative Tools?

The worst failures I see are not picking the wrong paradigm. They are using both without boundaries. Someone runs manual `aws ec2 modify-instance-attribute` after Terraform created the host. The next apply fights human changes. Or a declarative Kubernetes manifest tries to run database seeds on every sync. That is an imperative job dressed in YAML.

Mistake 1: manual changes outside state

SSH edits bypass drift detection. Document emergency fixes and backport them into IaC within 24 hours. For sites on shared pipelines like Notary Kathmandu, I treat emergency SSH as debt with a ticket, not a workflow.

Mistake 2: storing secrets in declarative files

Never commit plaintext database passwords in Terraform. Use environment-specific secret stores or CI variables. Validate JSON configs locally with a JSON formatter before they enter the pipeline. Declarative repos are cloned widely. Assume leaks.

Mistake 3: ignoring immutable alternatives

Declarative patching of long-lived VMs still accumulates entropy. For some workloads, immutable golden images with Packer reduce patch drift entirely. You declare the image version, not every apt package history.

Mistake 4: skipping plan review in CI

Declarative without review is just faster breakage. Wire `terraform plan` or equivalent into merge requests. Pair that with zero-downtime Terraform updates when load balancers and health checks are involved.

Jenkins users can mirror the same guardrails. A declarative Jenkins pipeline for app builds does not replace IaC plans. It complements them. Pipeline-as-code is declarative for CI; deploy scripts inside it may still be imperative.

How Do Real Production Stacks Combine Both Models in 2026?

A typical Laravel production stack on Ubuntu 24 in 2026 looks like this. Declarative layers provision the EC2 instance, Elastic IP, Route53 records, security groups, and RDS MySQL 8.4. Imperative layers install the release via Deployer, run `php artisan migrate --force`, and reload PHP-FPM 8.4. Redis 8.10 caching config may live in declarative env vars or imperative `.env` templating depending on team taste.

WordPress and WooCommerce shops follow the same split. Declarative DNS and CDN rules. Imperative plugin updates tested on staging first. For WooCommerce 11.1 on WordPress 7.1, I avoid declaring every plugin file in Terraform. That fight is not worth the module complexity.

Multi-language IaC tools blur the line. Pulumi lets you write IaC in PHP or JavaScript, which feels imperative in syntax but compiles to declarative resource graphs. Azure Bicep and Crossplane push declarative models into Kubernetes-native controllers. The paradigm label matters less than whether the engine reconciles state automatically.

Hybrid Stack: Laravel on AWSDeclarative Layer — TerraformVPC, EC2, RDS, Route53, IAM, TLS certsConfig Layer — Ansible / cloud-initPHP 8.4, Apache, UFW, fail2ban, Redis clientImperative Layer — GitLab CI + Deployer 7Git pull, Composer, migrate, symlink, FPM reloadRuntime — Laravel 13 app + MySQL 8.4 + Redis 8.10Business logic stays out of IaC files
Production declarative vs imperative infrastructure layers stacked for a typical Laravel deployment

Hosting choices affect how far you can go declarative. Shared cPanel-style hosting often limits you to imperative FTP uploads and manual cron edits. Managed VPS or EC2 unlocks full IaC. If you are choosing providers, factor that into domain registration and hosting decisions early. Moving later triggers website migration work you could avoid.

After launch, support and maintenance retainers should include quarterly drift audits. Run plan-only jobs. Compare cron paths after Deployer symlink swaps. Stale cron paths are a classic imperative failure I still catch on long-running Nepali client servers.

For enterprise apps with strict uptime targets, pair declarative networking with imperative blue-green deploys. Read GitOps for infrastructure vs application GitOps to see where pull-based sync helps and where push-based deploys stay simpler.

Key Takeaways

  • Declarative infrastructure defines desired state; the tool reconciles drift through plan and apply cycles.
  • Imperative infrastructure runs ordered commands—ideal for deploys, migrations, and service reloads.
  • Declare cloud foundations (networks, IAM, databases); script application releases and one-off fixes.
  • Manual SSH edits without IaC backport create undeclared drift that the next apply may destroy.
  • Hybrid stacks are industry normal in 2026—Terraform plus Deployer is a sane default for Laravel on Ubuntu.
  • Review declarative plans in CI before apply; treat emergency manual fixes as debt with a backport ticket.

People Also Ask

Is Terraform declarative or imperative?

Terraform is declarative. You describe resources and their properties in HCL. The Terraform engine builds a dependency graph, compares it to stored state, and produces a plan of changes. You do not script individual API calls in sequence. Provisioning hooks exist, but the core model is desired-state reconciliation.

Is Ansible declarative or imperative?

Ansible playbooks are written imperatively as ordered tasks, but many modules behave declaratively by checking current state before acting. A task might say “install nginx,” yet the module only installs if nginx is missing. That hybrid feel confuses beginners, but it is why Ansible works well for mutable server configuration.

Can you use declarative and imperative tools together?

Yes, and most production teams should. A common pattern declares cloud infrastructure with Terraform, configures the OS with Ansible, and deploys application code with CI plus Deployer or similar. Draw a hard boundary: never manage the same resource attribute from both toolchains without a documented owner.

Which is better for small teams in Nepal?

Small teams with limited DevOps time often start imperative because shell scripts and Deployer match existing PHP workflows. As client count grows, declarative DNS and firewall rules pay off quickly. You do not need a platform engineering department to version-control a Terraform module for TLS and security groups. Start hybrid, not purist.

Pick the Right Default for Your Next Deploy

Declarative vs Imperative Infrastructure is not a loyalty test. It is a boundary decision. Declare anything that should survive staff turnover and show up in audit logs. Script anything that touches live runtime behaviour and must run once per release. If your stack feels fragile after every deploy, map each failure to the layer it belongs in—cloud, config, or app—and fix the model there.

Need help untangling a mixed pipeline on Ubuntu, Laravel, or WordPress? Contact us for a practical infrastructure review. You can also browse portfolio projects that run declarative cloud layers with imperative zero-downtime deploys, or read more on the blog about enterprise application development and long-term testing and optimization for production systems.

Frequently Asked Questions

Declarative code describes the end state you want; imperative code lists ordered commands to run. Same production target, opposite control flow.

Terraform is declarative. You describe resources and properties in HCL; the engine builds a dependency graph, compares it to stored state, and produces a plan before anything touches production.

Ansible playbooks are written imperatively as ordered tasks, but many modules behave declaratively by checking current state before acting. That hybrid model suits mutable server configuration.

Drift is when live infrastructure no longer matches your declared desired state. Declarative tools like Terraform compare reality against the definition during plan cycles. If someone adds a security group rule manually via SSH, the next plan flags the mismatch. That feedback loop is why declarative IaC pairs well with GitOps-style review workflows. Without it, untracked manual edits accumulate until the next apply unexpectedly destroys or reverts human changes.

Yes, and most production teams should. A common pattern declares cloud infrastructure with Terraform, configures the OS with Ansible, and deploys application code through CI plus Deployer. The critical rule is drawing a hard boundary: never manage the same resource with both paradigms. On shared EC2 infrastructure I maintain for legal-tech sister sites, Terraform-style modules define VPC rules and DNS while Deployer handles imperative Laravel releases. Hybrid stacks are industry normal in 2026.

Lean declarative when the API exposes resources with stable IDs, auditors need a human-readable desired-state file, or you need multi-environment parity where staging must mirror production topology. Start declarative for anything touching billing: VPCs, RDS instances, S3 buckets, IAM roles. Cloud networking, databases, and load balancers with clear resource APIs fit declarative models well. Prefer declarative plans with guardrails when non-engineers might run changes, because the review step before apply acts as a safety rail small teams often skip with scripts alone.

Imperative workflows excel when the operation is procedural and order-dependent. Database migrations, cache warmers, payment-gateway webhook re-registration, and PHP-FPM reloads after deploy do not map cleanly to static resource blocks. On Laravel applications I deploy with Deployer 7, steps must run in sequence: Composer install, symlink swap, then reload PHP-FPM 8.4 because opcache keeps old bytecode until restart. One-time migrations and data backfills also belong in imperative scripts with logging, not declarative resource declarations.

Desired state is the target configuration your code declares: three app servers, one load balancer, TLS on port 443. Declarative tools treat your repo as the source of truth for this definition. The tool reconciliation loop continuously closes the gap between actual infrastructure and that declared target. When you commit a Terraform module and open a pull request, the plan output shows creates, updates, and destroys before anything touches production. This makes desired state readable, reviewable, and version-controlled rather than scattered across runbooks and tribal knowledge.

Idempotency means running the same operation twice produces the same result. Both paradigms chase this property, but declarative tools enforce it at the plan layer while imperative scripts require you to enforce it per task or module. Ansible modules expose idempotency flags to prevent double-install damage. Without idempotency, re-running a deploy script might duplicate records, reinstall packages unnecessarily, or restart services repeatedly. Infrastructure failures often come from scripts that assume yesterday's disk layout rather than checking current state before acting.

The worst failures come from using both without boundaries. Running manual aws ec2 modify-instance-attribute after Terraform created the host causes the next apply to fight human changes. Storing plaintext database passwords in declarative files is dangerous because IaC repos are cloned widely. Skipping plan review in CI turns declarative into faster breakage. Declarative Kubernetes manifests that run database seeds on every sync are imperative jobs dressed in YAML. For emergency SSH fixes on sites like Notary Kathmandu, I treat manual edits as debt with a ticket and backport into IaC within 24 hours.

A typical Laravel stack on Ubuntu 24 uses declarative layers to provision EC2 instances, Elastic IPs, Route53 records, security groups, and RDS MySQL 8.4. Imperative layers install releases via Deployer, run php artisan migrate --force, and reload PHP-FPM 8.4. Redis 8.10 caching config may live in declarative env vars or imperative .env templating. On a booking platform like Adventure Third Pole Trek, Laravel plus Livewire code ships through imperative CI while DNS and TLS stay declarative. Terraform plus Deployer is a sane default for Laravel on Ubuntu.

Generally no for full application lifecycle tasks. Trying to declare every Composer post-install hook in Terraform creates YAML soup. Deployer 7 performs zero-downtime symlink swaps imperatively by design because you cannot declare the site is deployed and expect the tool to infer Git fetch semantics. Application code changes daily while cloud topology changes rarely, so the split is intentional. Keep imperative scripts for releases, migrations, and service reloads you already understand. Reserve declarative tooling for cloud foundations: networks, IAM, databases, and DNS records.

Declarative rollback means reverting the commit and re-applying, or rolling back stored state to a previous version. Imperative rollback means running a previous script version or symlink back to the prior release directory. On Deployer 7 workflows, rolling back is often as simple as pointing the current symlink to the previous release and reloading PHP-FPM. Declarative rollbacks require understanding what the plan will destroy before apply, because reverting a module might remove resources created after the target commit. Each model needs its own documented rollback path rather than assuming one approach covers both layers.

SSH edits bypass drift detection entirely. When someone manually adds port 22 to a security group after Terraform defined HTTPS-only access, the next plan flags drift but the human change may have already exposed production. Worse, the next apply may destroy the manual fix without warning. Document emergency fixes and backport them into IaC within 24 hours. On shared pipelines I maintain, emergency SSH is debt with a ticket, not a workflow. Declarative without this discipline creates a false sense of safety while live servers become undebuggable snowflakes.

Mutable infrastructure means patching servers in place over time, which accumulates configuration entropy even with declarative tooling. Immutable infrastructure replaces servers from golden images rather than patching long-lived VMs in place. Tools like Packer let you declare the image version instead of every apt package history. For some workloads, immutable golden images reduce patch drift entirely. Declarative patching of long-lived VMs still allows untracked state to build up. The choice affects how you handle updates: incremental patches on existing hosts versus spinning up fresh instances from a known-good baseline and retiring old ones.

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: