USD ($)
$
United States Dollar
Euro Member Countries
India Rupee
د.إ
United Arab Emirates dirham
ر.س
Saudi Arabia Riyal

Declarative vs Imperative Approach in IaC

Lesson 14/24 | Study Time: 60 Min

When writing Infrastructure as Code, one of the first and most fundamental choices a team makes is how to express what they want their infrastructure to look like.

There are two primary approaches to this — the declarative approach and the imperative approach. These are not just technical styles; they represent two different ways of thinking about infrastructure management.

Understanding the difference between them is essential for anyone working with IaC tools, because the approach you choose shapes how you write configurations, how tools behave, and how maintainable your infrastructure code becomes over time.

Both approaches have their place, and many experienced DevOps practitioners use elements of both depending on the situation.

The Core Distinction Between Declarative and Imperative

The clearest way to understand the difference between declarative and imperative is through a single question:


Declarative asks: What should the infrastructure look like?

Imperative asks: How should the infrastructure be built, step by step?


In the declarative approach, you describe the desired end state of your infrastructure, what resources should exist, how they should be configured, and how they should relate to each other.

The IaC tool then figures out on its own what steps are needed to reach that state from wherever the infrastructure currently is.

In the imperative approach, you write explicit, step-by-step instructions telling the tool exactly what actions to perform — create this server, install this package, configure this setting, in this specific order.


A helpful everyday analogy makes this clear:


The Declarative Approach

In the declarative approach, the engineer writes a configuration file that describes the desired state of the infrastructure.

The IaC tool compares this desired state against the current actual state of the infrastructure and automatically determines what actions are needed to make reality match the definition.

The engineer does not need to specify how to get there — only what the result should be.


Key characteristics of the declarative approach:


1. You define the end goal, not the steps to reach it.

2. The tool manages the logic of how to achieve the desired state.

3. Running the same configuration multiple times always produces the same result — this is idempotency.

4. If infrastructure already matches the desired state, the tool makes no changes.

5. Changes to infrastructure are made by modifying the configuration file and re-applying it.


Tools that follow the declarative approach: Terraform, AWS CloudFormation, Kubernetes manifests, Puppet, and Azure Resource Manager templates all use a declarative style.

A Declarative Example — Terraform

The following Terraform configuration declaratively defines an AWS S3 storage bucket:


Notice what is absent from this configuration — there are no instructions about how to create the bucket. There is no sequence of steps, no conditional logic saying "if the bucket does not exist, create it."

The engineer simply states: this bucket should exist, with this name, with these tags. Terraform handles everything else.

If this configuration is applied when the bucket already exists with the correct settings, Terraform makes no changes. If the bucket does not exist, Terraform creates it. If a tag is different, Terraform updates only that tag.

The tool always drives the infrastructure toward the described state.

Strengths of the Declarative Approach


1. Simplicity of intent: Configuration files are clean, readable, and easy to understand because they describe outcomes rather than procedures.

2. Idempotency: Safe to run repeatedly without risk of creating duplicate resources or unintended side effects.

3. Self-documenting: The configuration file is an accurate, always-current description of what the infrastructure looks like.

4. Reduced cognitive load: Engineers focus on what they need, not on the mechanics of how to build it.

5. Easier maintenance: Changing infrastructure means editing the desired state; the tool calculates what needs to change.

Limitations of the Declarative Approach


1. Less control over execution order: When precise sequencing of steps is critical, declarative tools may require additional configuration to enforce it.

2. Debugging can be less intuitive: When something goes wrong, understanding why the tool made a specific decision can be harder than reading step-by-step imperative code.

3. Learning curve: Writing effective declarative configurations requires understanding how the tool interprets and applies desired state.

The Imperative Approach

In the imperative approach, the engineer writes explicit instructions, a sequence of commands or scripts that tell the tool exactly what to do and in what order.

The tool executes those instructions faithfully, one step at a time, without making its own decisions about what to do.

This is fundamentally similar to writing a traditional script or program — you are in direct control of every action the system takes.


Key characteristics of the imperative approach:


1. You define the exact steps and their sequence.

2. The tool executes instructions without inferring intent or desired state.

3. The engineer is responsible for handling conditions — checking if a resource exists before creating it, handling errors, and managing execution order.

4. Greater control over exactly what happens and when.

5. More flexible for complex, conditional, or multi-step workflows.


Tools and languages that follow the imperative approach: Ansible (partially), AWS CLI scripts, Bash scripts, Python with Boto3 (AWS SDK), Pulumi (when using general-purpose languages imperatively), and Chef are examples of imperative or partially imperative approaches.

An Imperative Example — AWS CLI Bash Script

The following bash script imperatively creates an AWS S3 bucket using the AWS Command Line Interface:


bash

#!/bin/bash


BUCKET_NAME="my-application-storage"

REGION="us-east-1"


# Check if bucket already exists

if aws s3api head-bucket --bucket "$BUCKET_NAME" 2>/dev/null; then

  echo "Bucket already exists. Skipping creation."

else

  echo "Creating bucket..."

  aws s3api create-bucket \

    --bucket "$BUCKET_NAME" \

    --region "$REGION"


  aws s3api put-bucket-tagging \

    --bucket "$BUCKET_NAME" \

    --tagging 'TagSet=[{Key=Environment,Value=Production},{Key=Team,Value=Platform}]'


  echo "Bucket created successfully."

fi


Notice how much more explicit this is compared to the Terraform example. The engineer must:


1. Manually check whether the bucket already exists.

2. Write conditional logic to handle both the existing and non-existing cases.

3. Explicitly call separate commands for creating the bucket and applying tags.

4. Handle the sequence of operations themselves.


The tool does exactly what it is told, nothing more, nothing less.

Strengths of the Imperative Approach


1. Precise control: Every action is explicitly defined; nothing happens that the engineer did not write

2. Flexibility: Complex conditional logic, loops, and dynamic decisions are straightforward to implement

3. Familiarity: Engineers with programming backgrounds find the imperative style intuitive

4. Transparency: It is always clear exactly what will happen when the script runs, in what order

5. Suited for one-time tasks: Data migrations, system bootstrapping, and ad-hoc operations are naturally expressed imperatively

Limitations of the Imperative Approach


1. Not inherently idempotent: Without careful engineering, running an imperative script twice can create duplicate resources, cause errors, or produce unintended side effects.

2. More code to write and maintain: Handling all conditions, error cases, and sequences requires significantly more code than an equivalent declarative configuration.

3. Harder to scale: As infrastructure grows in complexity, imperative scripts become difficult to maintain and reason about.

4. Not self-documenting: A script describes how infrastructure was built, but does not necessarily reflect what currently exists.

Many Tools Support Both

In practice, the line between declarative and imperative is not always sharp. Many IaC tools support both styles or blend elements of each.


Ansible is a particularly good example of this nuance. Ansible playbooks are written in YAML and look declarative on the surface — you describe tasks and desired states for configuration management.

However, Ansible also executes tasks in a defined sequence, which gives it an imperative character. Many practitioners describe Ansible as a hybrid — declarative in style but procedural in execution.

Pulumi takes a different approach entirely — it allows engineers to define infrastructure using general-purpose programming languages like Python, TypeScript, or Go. Depending on how it is written, Pulumi code can be either declarative or imperative in nature.

Terraform is strongly declarative, but it does support provisioners, blocks that run imperative scripts on resources after they are created — for situations where declarative configuration alone is not sufficient.

Which Approach is Right for DevOps Teams?

For most infrastructure provisioning and management tasks in a DevOps environment, the declarative approach is recommended as the default. Here is why:


1. Infrastructure defined declaratively is easier to review in pull requests — reviewers can clearly see the intended end state.

2. Declarative configurations stored in Git provide an accurate, living record of what infrastructure should look like.

3. Idempotency reduces the risk of accidental duplicate resources or configuration conflicts.

4. Most major cloud providers and platforms are designed to work naturally with declarative IaC tools.


The imperative approach remains valuable for specific scenarios, including:


1. Running one-time data migrations or system bootstrapping tasks.

2. Implementing complex conditional logic that declarative tools cannot express cleanly.

3. Writing automation scripts for operational tasks that are not part of ongoing infrastructure state.

4. Situations where fine-grained control over execution order is genuinely required.