Other Infrastructure as Code Tools
The wider landscape: CloudFormation, Bicep, Pulumi and the CDK, Crossplane, Puppet, Chef and Salt, PowerShell DSC, and how to choose.
The landscape at a glance
Terraform, OpenTofu and Ansible, the two families the rest of this topic uses, are not the only tools. The overview page divides tools into provisioning (creating cloud resources) and configuration management (setting up what runs inside a machine); a second useful question is whether a tool acts only when you run it, or keeps checking and correcting all the time.
The table compares the tools on this page with the two families the rest of the topic uses. Versions and licenses are as of October 2026.
| Tool | Manages | You write | Where it keeps track | License |
|---|---|---|---|---|
| Terraform / OpenTofu | Anything with a provider | HCL | A state file you store | BSL 1.1 / MPL 2.0 |
| AWS CloudFormation | AWS | YAML or JSON | AWS (the stack) | AWS service |
| ARM templates and Bicep | Azure | JSON, or Bicep | Azure (the deployment) | Bicep: MIT |
| Pulumi | Anything with a provider | TypeScript, JavaScript, Python, Go, .NET, Java or YAML | A state backend | Apache 2.0 |
| AWS CDK | AWS | TypeScript, JavaScript, Python, Java, C# or Go | CloudFormation | Apache 2.0 |
| Crossplane | Cloud resources, through Kubernetes | Kubernetes YAML | The Kubernetes API | Apache 2.0 |
| Ansible | Servers, network devices, some cloud | YAML playbooks | Nothing; checks each run | GPL 3.0 |
| Puppet / OpenVox | Servers | Puppet language | Puppet server | Commercial builds / Apache 2.0 |
| Chef Infra | Servers | Ruby-based recipes | Chef Infra Server | Apache 2.0 source, commercial builds |
| Salt | Servers | YAML with Jinja2 | Salt master | Apache 2.0 |
| PowerShell DSC v3 | Windows, Linux and macOS settings | YAML or JSON | Nothing; checks each run | MIT |
The examples below all describe the same thing where they can: a private, versioned storage bucket for Kestrel Energy's nightly billing exports, owned by Joost Bakker. None of these tools was installed on the test machine, so all output on this page is illustrative, written from each tool's documentation.
AWS CloudFormation
AWS CloudFormation is the infrastructure as code service built into Amazon Web Services. You write a template in YAML or JSON; CloudFormation creates the resources as one unit called a stack, remembers which real resources belong to it, and deletes them together when the stack is deleted. There is no state file to protect: AWS keeps that bookkeeping itself.
billing-exports.yaml:
AWSTemplateFormatVersion: "2010-09-09"
Description: Kestrel Energy bucket for the nightly billing exports
Parameters:
Environment:
Type: String
AllowedValues: [test, prod]
Resources:
BillingExports:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName:
Fn::Sub: kestrel-billing-exports-${Environment}
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: Owner
Value: joost.bakker
Outputs:
BucketName:
Value:
Ref: BillingExports
Fn::Sub substitutes the parameter into the name and
Ref returns the bucket's name. Most templates use the
short forms !Sub and !Ref; those are YAML
tags that only CloudFormation understands, so ordinary YAML tools
(and linters that do not know them) reject the file. The long form
above is valid plain YAML. DeletionPolicy: Retain
keeps the bucket and its exports even if the stack is deleted.
Change sets
A change set is
CloudFormation's preview: a list of what an update to a stack would
add, modify or remove, and whether a modification replaces a
resource, created without changing anything. It plays the role of
tofu plan, and it is what a reviewer approves before
someone executes it.
$ aws cloudformation deploy --template-file billing-exports.yaml \
--stack-name kestrel-billing-exports-prod \
--parameter-overrides Environment=prod --no-execute-changeset
Waiting for changeset to be created.. Changeset created successfully. Run the following command to review changes: aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:eu-west-1:111122223333:changeSet/awscli-cloudformation-package-deploy-1791277200/…
CloudFormation also offers drift detection (which resources were changed outside the stack) and StackSets, which roll the same stack out to many AWS accounts and Regions. Its limit is in the name: it manages AWS only. A DNS record at another provider, a GitHub repository or a monitoring dashboard needs another tool.
Azure: ARM templates and Bicep
On Azure, every deployment goes through Azure Resource Manager. An
ARM template (Azure Resource
Manager template) is the JSON format that Resource Manager deploys
directly: complete and precise, but verbose and hard to review.
Bicep is Microsoft's domain-specific
language for the same thing: during deployment, the Bicep CLI
converts a .bicep file into an ARM JSON template. Bicep
is the recommended way to write new templates; version 0.48.1 is
current (as of October 2026).
@allowed(['test', 'prod'])
param environment string
param location string = resourceGroup().location
resource billingExports 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: 'stkestrelbill${environment}'
location: location
sku: { name: 'Standard_ZRS' }
kind: 'StorageV2'
properties: {
allowBlobPublicAccess: false
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
}
tags: { owner: 'joost.bakker' }
}
output storageAccountName string = billingExports.name
The @2023-05-01 part pins the version of the Azure API
the resource definition follows, so a template keeps meaning the
same thing over the years. Azure has no state file either: it stores
the state of every deployment itself. The preview is called
what-if:
$ az deployment group what-if --resource-group rg-kestrel-billing-prod \
--template-file billing-exports.bicep --parameters environment=prod
Resource and property changes are indicated with this symbol: + Create The deployment will update the following scope: Scope: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-kestrel-billing-prod + Microsoft.Storage/storageAccounts/stkestrelbillprod [2023-05-01] … Resource changes: 1 to create.
Deployments are idempotent: deploying the same file twice changes nothing the second time. Google Cloud's counterpart is Infrastructure Manager, which runs Terraform configurations for you (see native tools on the Cloud page).
Infrastructure in a programming language
HCL, YAML and Bicep are configuration languages: loops and conditions exist, but they are limited on purpose. Some teams would rather use the language they already know, with its editor support, test frameworks and package manager. Two tools are built for that.
Pulumi
Pulumi is an infrastructure as code tool in which you describe resources in TypeScript, JavaScript, Python, Go, .NET languages, Java or YAML. Running the program does not create anything directly: it registers the desired resources, and the Pulumi engine compares them with its state and works out the changes, like a Terraform plan. State lives in Pulumi Cloud or in a backend you run yourself, such as an object storage bucket. Pulumi is open source under Apache 2.0 (version 3.267.0, as of October 2026).
"""Kestrel billing export buckets, one per environment (Pulumi, Python)."""
import pulumi
import pulumi_aws as aws
for env in ["test", "prod"]:
bucket = aws.s3.Bucket(
f"billing-exports-{env}",
tags={"Owner": "joost.bakker", "Environment": env},
)
aws.s3.BucketVersioning(
f"billing-exports-{env}-versioning",
bucket=bucket.id,
versioning_configuration={"status": "Enabled"},
)
pulumi.export(f"bucket_{env}", bucket.bucket)
pulumi preview shows the planned changes and
pulumi up applies them. The ordinary
for loop is the point: anything Python can compute, the
configuration can use. That is also the risk, because a clever
program is harder to review than a plain list of resources.
AWS Cloud Development Kit
The
AWS Cloud Development Kit
(CDK) is a framework for describing AWS infrastructure in
TypeScript, JavaScript, Python, Java, C# or Go. It does not deploy
anything itself: cdk synth turns the program into a
CloudFormation template, cdk diff compares it with the
deployed stack, and cdk deploy hands it to
CloudFormation. Its strength is constructs, reusable
building blocks with safe defaults. Version 2.272.0 is current (as
of October 2026), under Apache 2.0.
from aws_cdk import App, RemovalPolicy, Stack
from aws_cdk import aws_s3 as s3
from constructs import Construct
class BillingExportsStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
s3.Bucket(
self,
"BillingExports",
versioned=True,
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
enforce_ssl=True,
removal_policy=RemovalPolicy.RETAIN,
)
app = App()
BillingExportsStack(app, "kestrel-billing-exports-prod")
app.synth()
One line, enforce_ssl=True, adds a bucket policy that
refuses unencrypted connections; the construct writes the policy for
you. Read the synthesized template at least once, so you know what a
construct really creates.
Crossplane: infrastructure through Kubernetes
Crossplane turns a Kubernetes cluster into a control plane for cloud resources: a bucket or a database becomes a Kubernetes object, and a Crossplane provider running in the cluster creates the real resource and keeps it matching the object, like any other Kubernetes controller. It is a CNCF project, graduated in October 2025; Crossplane 2 appeared in August 2025 and 2.4.2 is current (as of October 2026).
apiVersion: s3.aws.m.upbound.io/v1beta1
kind: Bucket
metadata:
name: kestrel-billing-exports-prod
namespace: billing
spec:
forProvider:
region: eu-west-1
tags:
Owner: joost.bakker
$ kubectl get buckets -n billing
NAME SYNCED READY EXTERNAL-NAME AGE kestrel-billing-exports-prod True True kestrel-billing-exports-prod 3m12s
The difference with Terraform is timing: Terraform acts when someone
runs apply, while Crossplane reconciles continuously,
so a change made by hand in the console is reverted within minutes.
Platform teams use Crossplane's compositions to offer simple custom
resources ("a Kestrel database") that expand into many cloud
resources, and deliver them with
GitOps. The price is that you need, and must protect, a Kubernetes
cluster that holds credentials for your cloud.
Puppet, Chef and Salt
Before Ansible became common, configuration management meant one of these three. All three manage the inside of servers (packages, files, services, users), and all three traditionally run an agent on every machine that regularly pulls its configuration from a central server and corrects drift on its own, where Ansible pushes over SSH only when someone runs a playbook (see other tools on the Network Automation page). Each example below writes the same file: the billing export's settings.
Puppet
Puppet is a declarative configuration management tool with its own language: you declare resources and their relationships, and the agent on each node (by default every 30 minutes) fetches a compiled catalog from the Puppet server and enforces it.
file { '/etc/kestrel/billing-export.conf':
ensure => file,
owner => 'billing',
group => 'billing',
mode => '0640',
content => "EXPORT_DIR=/srv/billing/out\n",
}
Puppet has belonged to Perforce since 2022. According to Perforce's announcement in late 2024, its official Puppet packages have been published since 2025 in a private repository under a commercial license, free only for small installations; the public source repository stops at version 8.10.0. The community responded with OpenVox, a fork by the Vox Pupuli community under Apache 2.0; OpenVox 9.0.0 was released on 2 October 2026. Check which one an organization runs before following a tutorial.
Chef Infra
Chef Infra describes configuration as recipes written in a Ruby-based language, grouped in cookbooks. Resources in a recipe run in the order written; the Chef Infra Client on each node pulls its cookbooks from a Chef Infra Server and converges the node. Chef has been owned by Progress Software since 2020; the source code is Apache 2.0, but Progress's own builds require accepting a license, and community builds exist under other names (such as Cinc). Chef Infra Client 19 is the current major version (as of October 2026).
file '/etc/kestrel/billing-export.conf' do
content "EXPORT_DIR=/srv/billing/out\n"
owner 'billing'
group 'billing'
mode '0640'
end
Salt
Salt (also
SaltStack) is a configuration management and remote execution tool
built around a fast event bus: a master sends
states and commands to minions (agents), and can
react to events from them. It can also work without agents over SSH
(salt-ssh). States are YAML with Jinja2 templating.
Salt passed from SaltStack to VMware in 2020 and with VMware to
Broadcom; Salt 3008 is the current long-term support release (as of
October 2026). Not to be confused with the salt in password
hashing.
/etc/kestrel/billing-export.conf:
file.managed:
- contents: EXPORT_DIR=/srv/billing/out
- user: billing
- group: billing
- mode: "0640"
PowerShell DSC
PowerShell DSC (Desired State
Configuration) is Microsoft's declarative configuration tool. The
classic version, built into Windows PowerShell 5.1, compiled
configurations to MOF files and enforced them with the Local
Configuration Manager service on Windows. DSC v3,
generally available since 12 March 2025, is a new, cross-platform
tool: a dsc command for Windows, Linux and macOS that
reads configuration documents in YAML or JSON, with no background
service. Resources can be written in any language. Version 3.3.0 is
current (as of October 2026), under the MIT license.
billing-export.dsc.config.yaml:
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Billing export folder setting
type: Microsoft.Windows/Registry
properties:
_exist: true
keyPath: HKLM\SOFTWARE\Kestrel\BillingExport
valueName: ExportDir
valueData:
String: D:\Billing\Out
$ dsc config test --file billing-export.dsc.config.yaml
$ dsc config set --file billing-export.dsc.config.yaml
test reports whether the machine is in the desired
state; set makes it so. Because DSC v3 has no agent,
something else decides when it runs: a scheduled task, a pipeline,
or another configuration tool that calls it. PowerShell itself is
covered in the
Scripting topic.
How to choose
There is rarely one right tool. These questions narrow it down quickly:
- What do you manage? One cloud only: its native tool (CloudFormation, Bicep) is well supported and needs no state storage. Several clouds or SaaS services as well: Terraform, OpenTofu or Pulumi. The inside of servers: Ansible, Puppet, Chef, Salt or DSC, usually together with a provisioning tool.
- Who writes and reviews it? Operations teams tend to read configuration languages more easily; development teams may prefer Pulumi or the CDK. Everyone who approves changes must be able to read the code and the preview.
- On demand or continuous? Run-on-demand tools (Terraform, Ansible) need a scheduled drift check; agent and controller tools (Puppet, Crossplane) correct drift themselves but need servers or a cluster to run on.
- License and governance. Since 2023, Terraform, Vault and Packer moved to the Business Source License, Puppet's packages became commercial, and CDKTF was archived. Tools under open-source licenses and neutral foundations (OpenTofu, Crossplane, OpenVox) protect against one vendor changing the rules, but check their activity and support too.
- What do you already run? A second tool for the same job doubles the knowledge, review effort and failure modes.
Kestrel Energy recorded its choice in an architecture decision: the standard is OpenTofu for cloud resources (open license, the team already knew Terraform) and Ansible for servers such as db01 and the web hosts. The meter-api team, which came with an acquisition, keeps its AWS CDK code because it produces reviewable CloudFormation change sets; new services follow the standard. Images and first-boot configuration, the remaining piece, are covered on Images and Bootstrapping.