I am trying to understand how Ansible is procedural and Terraform is not. All blogs seem to suggest an example - if you create 2 EC2 instances in Ansible and change the count to 4, you will have 4 more; but in Terraform you will have a total of 4 and not 4 more EC2 instances created when you change the count.

But I believe this is not really an example of procedural/declarative?

Is there a better example to explain why Ansible is procedural and Terraform is declarative?


1 Answer 1


These broad categorizations are only useful to the extent that we know what we mean by them, and unfortunately (but inevitably) everyone has their own sense of what "declarative" means, and therefore I think it's not super useful to make general statements like "Terraform is declarative".

Instead, I think it's worth digging deeper and talking about some characteristics of these systems that relate to the ideas of declarative programming. In particular, I'm going to focus on the idea of explicit vs. implicit control flow as described in the opening of that Wikipedia article at the time I write this, but I will say up front that (as we can see on that Wikipedia page) there are various other definitions of declarative programming that do not focus on control flow, and thus would presumably lead to a different analysis.

(Disclosure: I work on Terraform at HashiCorp and I designed the most recent iteration of the Terraform language. Therefore although I intend to do my best to describe both systems objectively, I know Terraform a lot better than I know Ansible and so my statements about Ansible are coming from my perspective as a user of it, while my statements about Terraform are colored by my knowledge of its implementation too.)


One thing we can say about the Terraform language is that the information we write in there is saying what should exist, not describing a series of steps for making that be true. When we run terraform plan (or when Terraform runs it implicitly as part of terraform apply), Terraform takes a configuration that is broadly-speaking composed of nouns (an EC2 instance, a cloud run definition, a virtual network), and Terraform itself turns that into a set of verbs (create the EC2 instance, update the cloud run definition, delete the virtual network).

Another dynamic-programming-related aspect of Terraform is that there are generally not explicit statements about the ordering of operations in a Terraform configuration. Just as Terraform itself produces the planned set of actions, Terraform also itself determines the ordering of those actions and which ones can potentially be taken concurrently. We could say that Terraform is using data flow (the way values propagate from one resource to another via expressions) to infer control flow.

In particular I'd note that it's not true to say that Terraform does not have control flow at all: there is still the idea of a correct sequence of operations that is required to produce the intended result. But that control flow is implied by the propagation of data between objects, not explicitly described by the author.

Although users often use the word "loops" to describe Terraform's repetition constructs like for expressions, resource for_each, and dynamic blocks, they are designed more like functional programming combinators like Map, where we're describing the transformation of one value into another rather than a sequence of operations to take. Terraform can implement that transform using various control paths, as long as the end result matches what the user described via expressions.

The provisioner concept in Terraform makes Terraform a bit of a hybrid in total: provisioners are a user-specified sequence of imperative steps to be taken as part of a "create" or "destroy" step. Terraform contains provisioners as a measure of pragmatism, because for stateful objects like virtual machines we often must use imperative techniques to ensure that side-effects appear in a particular order that Terraform would not naturally be able to infer. With that said, Terraform's provisioners are documented as being a last resort specifically because they escape from the usual model, and leave Terraform unable to fully model the set of actions that are being taken. Terraform also lacks constructs for writing dynamic control flow within the language using provisioners: there's no mechanism to write loops and conditional branches, and instead provisioners are (from Terraform's perspective) just a flat, fixed sequence of operations.


The tasks list in an Ansible playbook is an ordered sequence of verbs dictating an action to be taken. Many of these task types are presented as "Ensure that..." though, like "Ensure that /etc/foo exists and contains this content", so I think it's fair to say that these tasks are declarative statements presented in the form of a checklist: if the file already exists and already has that content, then from the perspective of an outside observer that task will take no action at all.

Some of Ansible's tasks are the sorts of things Terraform would delegate to provisioners though, like "run this separate program for its side-effects". In that case, as in Terraform with provisioners, it becomes the author's responsibility to understand the side-effects and ensure that the steps are happening in the correct order for those side-effects to make sense.

Control flow is an area where Terraform and Ansible's models diverge more significantly. As I noted previously, a task list is an ordered sequence, and so the author must explicitly specify an appropriate ordering of the actions described in that task list. Ansible does not automatically infer control flow from data flow as Terraform does.

Ansible has loops that are more like their imperative programming namesake. In common cases they are in principle similar to functional programming combinators, but for task loops in particular the fact that tasks themselves can have side-effects means that the order of execution of the loop is significant and therefore Ansible loops are explicit control flow.

Based on the above characteristics, my sense is that both Terraform and Ansible can be used both in a declarative way and in a procedural way. However, Terraform's language is designed to prioritize declarative-programming-style solutions to problems and constrain/discourage procedural approaches, while Ansible prioritizes procedural approaches while perhaps allowing an author to choose to take a declarative approach.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .