top of page

What Is Infrastructure as Code (IaC)? How It Works, Benefits, Best Practices & Choosing the Right Tools (2026)

11 minutes ago
25 min read
Infrastructure as Code (IaC) with cloud servers, code, and automation.

Most infrastructure outages are not caused by exotic failures. They are caused by a manual change nobody documented, a console click nobody reviewed, or a server nobody remembers building. Infrastructure as Code exists to close that gap: it turns servers, networks, and cloud services into text that a team can read, test, version, and review before anything actually changes. This guide explains what IaC really is, how it works under the hood, where it genuinely helps, where it introduces new risk, and how to choose between tools like Terraform, OpenTofu, Pulumi, CloudFormation, CDK, Bicep, Google Cloud Infrastructure Manager, and Crossplane without picking a false universal winner.

TL;DR

  • Infrastructure as Code (IaC) means defining servers, networks, and cloud services as text, then using a tool to create and update the real infrastructure from that text.

  • Most IaC tools are declarative (you describe the desired end state), though some, like AWS CDK and Pulumi, let you author that state using a general-purpose programming language.

  • Drift, state, and secrets are the three operational risks that matter most; none of them are solved just by adopting IaC.

  • Terraform, OpenTofu, Pulumi, CloudFormation, CDK, Bicep, Google Cloud Infrastructure Manager, and Crossplane all take different approaches to state and scope, and none of them is the universally correct choice.

  • IaC benefits (repeatability, review, auditability) only materialize with disciplined practices: version control, plans/previews, testing, and least-privilege CI/CD credentials.


What is Infrastructure as Code? (Quick Answer)

Infrastructure as Code (IaC) is the practice of defining and managing servers, networks, and cloud resources through machine-readable configuration files instead of manual setup. Teams store these files in version control, then use a tool to provision, update, or tear down infrastructure automatically, making changes repeatable, reviewable, and consistent across environments.


What is the single biggest Infrastructure as Code challenge your organization faces today?

  • 0%Choosing or standardizing the right IaC tool

  • 0%Migrating existing or manually managed infrastructure to IaC

  • 0%Managing state, drift, and multiple environments

  • 0%Security, compliance, and governance


Table of Contents

What Is Infrastructure as Code (IaC)?

Infrastructure as Code is the practice of defining computing infrastructure, such as virtual machines, networks, load balancers, databases, and permissions, as text files rather than configuring them by hand through a console or a script run ad hoc. A tool reads those files and calls the relevant cloud or platform APIs to create, change, or remove the described resources.

What counts as infrastructure in this context is broad: compute instances, container clusters, storage buckets, DNS records, identity and access policies, VPCs and subnets, managed databases, queues, and increasingly SaaS configuration such as monitoring dashboards or CI/CD pipelines. If a platform exposes an API to manage it, it can usually be brought under IaC.

Manual infrastructure management means an engineer logs into a console or SSHes into a box and makes a change directly. It works for a single server, but it does not scale: nothing records why a setting changed, two environments quietly diverge, and reproducing a server after a failure depends on someone's memory.

What 'code' means here: IaC files are usually declarative configuration (HCL, YAML, JSON, Bicep) or, in tools like Pulumi and AWS CDK, actual code in a general-purpose language such as TypeScript, Python, or Go. Either way, the file is the source of truth, it lives in a repository, and it goes through the same review habits as application code: diffs, pull requests, and history.

IaC applies to public cloud, private cloud, and on-premises virtualization alike. The mechanism differs (cloud APIs versus a hypervisor or bare-metal provisioning API), but the underlying idea, describe the desired result and let a tool reconcile reality to match it, is the same everywhere.

Aspect

Manual infrastructure

Infrastructure as Code

Change record

Exists only if someone writes it down

Exists as a diff in version control

Repeatability

Depends on memory and screenshots

Re-runnable from the same files

Review

Rarely reviewed before it happens

Reviewable via pull request, before it happens

Environment parity

Drifts over time

Same definition can build every environment

Recovery

Rebuild from memory and backups

Rebuild by re-applying the configuration

Why Infrastructure as Code Matters

Cloud infrastructure grew from a handful of servers to hundreds or thousands of resources spun up and torn down by API call. At that scale, manual management is not just slow, it is unreliable: nobody can hold the full topology of a modern cloud account in their head, and every hands-on change is a chance for an undocumented, unreviewed difference to creep in.

IaC addresses several concrete operational problems at once. Repeatability means the same configuration produces the same result in staging and production. Collaboration means infrastructure changes go through pull requests instead of living in one engineer's terminal history. Auditability means every change has an author, a timestamp, and a reason, because it is a commit.

It also supports environment parity (dev, staging, and production built from the same modules with different variables), disaster recovery (rebuilding a region from configuration instead of tribal knowledge), and governance, since policy checks can run against a plan before anything is deployed rather than after an auditor finds a problem.

None of this is automatic. IaC creates the opportunity for these benefits; an organization still has to adopt the review habits, testing, and access controls that turn the opportunity into a real outcome.

How Infrastructure as Code Works

Although tools differ in detail, most IaC workflows follow a recognizable lifecycle:

  1. Define the desired infrastructure in configuration files or a program.

  2. Store those definitions in version control alongside the rest of the codebase.

  3. Validate, lint, and test the changes before anything is deployed.

  4. Generate a plan or preview, where the tool supports one, showing exactly what will change.

  5. Have a human or an automated policy review that plan.

  6. Authenticate to the target cloud or platform's API using a scoped identity.

  7. Call the provider's API to create, update, or delete the affected resources.

  8. Record or observe the resulting state, depending on the tool's model.

  9. Detect drift if the deployed resources no longer match the configuration.

  10. Iterate through further pull requests and automation runs.

A few mechanisms recur across this lifecycle. A provider is the plugin or SDK that translates the tool's generic actions into calls against one platform's API (AWS, Azure, Google Cloud, Kubernetes, and hundreds of SaaS products all have providers in ecosystems like Terraform's). A dependency graph lets the tool figure out that a subnet must exist before a virtual machine can be placed inside it, so resources are created, updated, or destroyed in a safe order rather than the order they were typed.

Idempotency means applying the same configuration twice produces the same result the second time, rather than creating a duplicate resource; well-built providers detect that a resource already matches the desired configuration and do nothing. A plan or preview shows the calculated difference between desired and current state before any API call runs, which is what lets a reviewer catch a destructive change before it happens, not after.

State and reconciliation are the parts that vary most between tools. Some systems, such as Terraform, OpenTofu, and Pulumi, keep an explicit state file or state store that records what the tool believes exists, and every plan is a comparison between configuration, state, and (optionally) a live refresh of the real infrastructure. Others, such as Bicep and CloudFormation, ask the cloud platform itself (Azure Resource Manager or AWS CloudFormation) to track state, so there is no separate file for a team to manage. Crossplane takes a third path: it runs continuously inside Kubernetes and reconciles infrastructure on a loop rather than on a one-shot apply.

Core Infrastructure as Code Concepts

  • Desired state — the infrastructure described in the configuration, i.e. what should exist.

  • Actual state — what is really deployed on the target platform right now.

  • Resource — a single managed object: a virtual machine, a bucket, a DNS record, an IAM role.

  • Provider — the plugin that lets the tool talk to a specific platform's API.

  • Module or component — a reusable, parameterized bundle of resources, such as 'a standard VPC' or 'a standard web service'.

  • Dependency — a relationship that forces one resource to be created, updated, or destroyed before or after another.

  • Variable / parameter — an input that lets the same configuration produce different environments.

  • Output — a value a module exposes for use elsewhere, such as a load balancer's DNS name.

  • State — the tool's record of what it believes is deployed; not universal across tools.

  • Backend — where state is stored: locally, in object storage, or in a managed service.

  • Locking — a mechanism that prevents two people from applying changes to the same state at once.

  • Idempotency — running the same configuration repeatedly converges on the same result.

  • Drift — a mismatch between desired configuration and what is actually deployed.

  • Reconciliation — the process of bringing actual state back in line with desired state.

  • Plan / preview — a computed, human-readable diff shown before changes are applied.

  • Workspace / environment — a named instance of a configuration, e.g. dev versus prod.

  • Policy as code — machine-checked governance rules evaluated against a plan or a resource graph.

Declarative vs. Imperative IaC

Declarative IaC describes the end state you want: 'there should be three web servers behind this load balancer.' The tool figures out the steps needed to get there, and figures out again what to do if you later change the number to five. Terraform, OpenTofu, CloudFormation, and Bicep all work this way.

Imperative IaC describes the steps to take: 'create a server, then attach it to this load balancer, then repeat twice more.' Classic shell scripts and some configuration-management playbooks lean imperative, since they describe a sequence of actions rather than a target state.

In practice the line blurs. Pulumi and AWS CDK let you write imperative-looking code (loops, conditionals, functions) that ultimately compiles down to a declarative deployment plan or template. The value of the general-purpose language is expressiveness and tooling, not a fundamentally different execution model underneath.

Aspect

Declarative

Imperative

You specify

The desired end state

The steps to reach a state

Tool's job

Diff current vs. desired, then reconcile

Execute your steps in order

Idempotency

Usually built in

Must often be handled explicitly

Typical tools

Terraform, OpenTofu, CloudFormation, Bicep

Shell scripts; some CM playbooks

Trade-off

Less control over exact steps

More control, more to get wrong

IaC vs. Configuration Management vs. GitOps vs. Containers

These four terms get used interchangeably, and they should not be. IaC provisions infrastructure resources: it creates or removes the VM, the bucket, the network. Configuration management tools such as Ansible, Chef, and Puppet configure what runs inside a resource that already exists: installing packages, writing config files, managing users and services on a server.

GitOps is an operating model, not a provisioning tool: it says that a Git repository is the single source of truth and that an in-cluster or in-pipeline controller continuously reconciles the live system toward what is in Git. GitOps commonly uses IaC tools underneath it, but the two concepts are not synonymous; you can practice IaC without GitOps (a human runs `terraform apply` from a laptop) and you can practice GitOps with tools that aren't classic IaC engines.

Containers package an application and its dependencies into a portable image; they do not replace IaC. Something still has to provision the cluster, the network, the load balancer, and the storage the containers run on, and that something is usually IaC. Kubernetes manifests configure workloads inside a cluster that IaC (or Crossplane) typically created.

Approach

Primary purpose

Typical object managed

Operating model

IaC

Provision infrastructure

VMs, networks, managed services

Plan/apply or continuous reconciliation

Configuration management

Configure existing hosts

OS packages, files, services

Push or pull runs, often idempotent scripts

GitOps

Operating model for delivery

Any Git-declared system state

Continuous reconciliation from Git

Containers

Package and run applications

Application images and runtime

Scheduler-managed workloads

These approaches complement each other in most real stacks: IaC provisions the Kubernetes cluster and its networking, a GitOps controller keeps workload manifests in sync with Git, and configuration management or container images handle what runs on individual hosts.

Benefits of Infrastructure as Code

Repeatability and consistency: because the same files produce the same infrastructure, environments stop drifting apart from each other by accident. A staging environment built from the same module as production, with different variables, behaves predictably.

Speed: standing up a new environment becomes a matter of running the existing configuration with new parameters, rather than re-deriving the setup from documentation or memory.

Versioning and peer review: infrastructure changes go through the same pull-request workflow as application code, so a second engineer sees the diff, the plan output, and the reasoning before it ships.

Auditability: every change has an author, a timestamp, and a linked ticket or pull request, which matters both for incident response and for compliance evidence.

Disaster recovery and reconstruction: a region or account can be rebuilt from configuration rather than from an engineer's memory of how it was originally set up, provided state and secrets are also recoverable.

Governance and testing: policy-as-code checks and automated tests can run against a plan before anything is deployed, catching a misconfiguration in a pull request instead of in production.

None of these benefits are automatic consequences of adopting a tool. They depend on the organization actually using version control, requiring review, running plans before applies, and maintaining the discipline over time; a team that writes IaC but skips review and testing keeps the risk of manual changes while adding the complexity of a new toolchain.

Limitations, Risks, and Challenges of IaC

  • Learning curve — HCL, a provider's resource model, and state semantics take real time to learn well.

  • State complexity — a corrupted, lost, or out-of-sync state file can block every future change in tools that use one.

  • Drift — infrastructure changed outside the tool (a console edit, an emergency fix) silently diverges from configuration.

  • Secrets exposure — credentials committed to a repository or written in plaintext state are a common, preventable breach vector.

  • Destructive changes — a renamed resource can be read as delete-then-recreate, which is catastrophic for a stateful database.

  • Blast radius and permissions — an overly broad CI identity can change far more than a given pipeline should touch.

  • Provider/API changes — a cloud API update can break a provider version pinned years earlier.

  • Dependency complexity — large graphs of interdependent modules become slow to plan and hard to reason about.

  • Over-abstraction and monoliths — one giant root module covering an entire account is slow to plan and risky to change.

  • Lock-in — heavy investment in one tool's module ecosystem or one cloud-native DSL raises the cost of switching later.

  • Licensing and governance shifts — a vendor can change a tool's license or roadmap, as happened with Terraform's move to the Business Source License.

  • Debugging complexity — a failed apply partway through can leave infrastructure in a state that is hard to reason about.

  • False confidence — a clean plan output is not proof the change is correct or safe, only that it matches the tool's model.

Mitigations exist for most of these: pin provider versions deliberately, review plans line by line before destructive changes, keep state remote and backed up, scan for committed secrets, and grant CI identities the minimum permissions a pipeline actually needs.

A Practical Infrastructure as Code Example

The example below uses Terraform-compatible HCL (the same syntax OpenTofu reads) to declare a single AWS S3 bucket. It illustrates the lifecycle without pretending every tool shares this exact syntax or state model.

resource "aws_s3_bucket" "reports" {
  bucket = "acme-quarterly-reports"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Running the plan command compares this configuration against the tool's recorded state and the real bucket (if one already exists), then prints exactly what would be created, changed, or destroyed. Only after a human or an automated gate approves that plan does the apply step call the AWS API to create the bucket. A CDK or Pulumi program achieves the same outcome by instantiating a bucket object in TypeScript or Python; the code is different, but it still gets synthesized down to a declarative template or plan before anything touches AWS.

Infrastructure as Code in CI/CD

A mature IaC pipeline treats infrastructure changes with the same rigor as application deploys, typically in this order: validation, formatting and linting, security and static analysis, automated tests, a plan or preview, human or policy review, approval, apply, post-deploy verification, and ongoing drift monitoring.

  1. A pull request changes a configuration file.

  2. CI runs format checks and linting to catch syntax and style problems early.

  3. Static analysis and security scanners check for known misconfigurations (open security groups, public buckets, missing encryption).

  4. Automated tests run against the module, where the ecosystem supports it.

  5. CI generates a plan and posts it on the pull request so reviewers see exactly what will change.

  6. A human reviewer, and optionally a policy-as-code gate, approves or blocks the change.

  7. On merge, a pipeline applies the approved plan using a scoped, short-lived identity.

  8. Post-deploy checks verify the result, and drift monitoring watches for future out-of-band changes.

Saved plans matter for destructive changes: applying the exact plan a reviewer approved, rather than re-planning at apply time, avoids a race where the infrastructure changed between review and deploy. Separation of duties, where the identity that can approve a pull request is not the same identity that holds apply credentials, closes a common audit gap.

Infrastructure as Code Best Practices

Version control everything. Configuration with no history is not meaningfully different from a console change; a repository gives you diffs, blame, and rollback.

Keep changes small and reviewable. A pull request that touches one logical change is reviewable in minutes; one that touches forty resources at once gets rubber-stamped.

Build reusable modules with clear contracts. A module's inputs and outputs are its API; document them and treat breaking changes to a shared module as seriously as a breaking API change.

Pin and manage provider and dependency versions deliberately. Unplanned upgrades are a leading cause of surprise plan diffs and broken applies.

Separate environments using distinct state, distinct credentials, and, where the blast radius justifies it, distinct accounts or subscriptions, rather than one shared configuration switched by a variable alone.

Protect state: use a remote, versioned backend, enable locking where the tool supports it, and back it up. A lost or corrupted state file can block every future change.

Never hard-code secrets. Pull credentials from a secrets manager or a short-lived token exchange at runtime, not from a variable committed to the repository.

Use least-privilege, short-lived CI credentials, ideally via OIDC federation rather than long-lived static keys stored in CI secrets.

  • Run linting and validation on every pull request, not just before a release.

  • Add security scanning for common misconfigurations before merge.

  • Test infrastructure changes where the ecosystem supports it, including plan-diff assertions.

  • Always generate and review a plan or preview before a production apply.

  • Require peer review and, for sensitive environments, a second approver.

  • Adopt policy as code where governance requirements justify the overhead.

  • Run drift detection on a schedule, not only when something breaks.

  • Avoid one giant root module; split by blast radius and team ownership.

  • Assign clear ownership for every module and pipeline.

  • Plan tool and provider upgrades deliberately, with a rollback path.

  • Maintain a documented import or adoption process for infrastructure that predates IaC.

  • Review destructive changes (renames, replacements, deletions) with extra scrutiny.

  • Treat the IaC pipeline itself as a production system with its own access controls and monitoring.

IaC Security and Compliance

IaC does not make infrastructure secure by itself; it makes insecure infrastructure just as repeatable as secure infrastructure. A public storage bucket defined in a module gets recreated identically everywhere that module is used, misconfiguration included.

  • Secrets handling: never commit plaintext credentials; pull them from a secrets manager at runtime and treat any accidental commit as a rotation event, not just a deletion.

  • State security: state can contain sensitive values in plaintext depending on the tool and resource type; restrict who can read the backend and encrypt it where supported.

  • Least privilege: scope CI identities and human roles to only the actions and resources a given pipeline or person actually needs.

  • Short-lived credentials: prefer OIDC federation or temporary tokens over static, long-lived access keys.

  • Scanning: run static analysis for misconfigurations (open ingress, public storage, missing encryption) before merge.

  • Policy enforcement: use policy-as-code gates for rules that must never be violated, not just documented as guidelines.

  • Auditability: keep an immutable record of who approved and applied each change, tied to the commit and the plan output.

  • Separation of duties: the reviewer, approver, and the identity that applies the change should not all collapse into one person or one token.

  • Supply-chain risk: providers, modules, and plugins are third-party code; pin versions and review sources you did not author.

  • Sensitive outputs: mark secrets-bearing outputs so they are not printed in plan logs or CI console output.

Compliance frameworks generally want evidence of controlled, reviewed change, which IaC can produce, but only if the review and approval steps described above are actually enforced rather than optional.

Managing State, Secrets, and Infrastructure Drift

State is not universal. Terraform, OpenTofu, and Pulumi keep an explicit state file or state store as their record of what exists. CloudFormation and Bicep instead lean on the cloud platform's own tracking (CloudFormation stacks, Azure Resource Manager deployment history), so there is no separate file for a team to manage or lose. Crossplane represents state as live Kubernetes objects that its controllers continuously watch.

Git history and infrastructure state solve different problems. Git records what your configuration said at each point in time. State records what the tool believes is actually deployed right now, which can diverge from configuration through drift, partial applies, or manual intervention. Neither one substitutes for the other.

  • Remote state / backends: store state in a shared, durable location (object storage, a managed service) instead of a laptop.

  • Locking: prevent two concurrent applies from corrupting the same state, where the backend supports it.

  • Encryption: encrypt state at rest and in transit, since it may contain sensitive resource attributes.

  • Backups: keep versioned backups of state so a bad apply or accidental deletion is recoverable.

  • Access control: restrict who can read or write state to those who genuinely need it.

  • Imports: bring existing, hand-created resources under management deliberately, verifying the resulting plan shows no unintended changes.

  • Drift detection: run scheduled checks that compare configuration against real infrastructure, independent of when someone happens to run an apply.

  • Reconciliation: decide in advance whether drift gets overwritten automatically, flagged for review, or investigated as a possible incident.

  • Break-glass procedures: define, in advance, how emergency manual changes get reconciled back into IaC afterward rather than left to drift silently.

Testing Infrastructure as Code

Testing capability varies significantly by ecosystem, but a mature pipeline layers several kinds of checks:

  • Format and syntax checks catch typos and style issues before anything else runs.

  • Validation confirms the configuration is internally consistent and provider schemas are satisfied.

  • Linting enforces house style and catches common mistakes.

  • Static and security analysis checks for known misconfiguration patterns.

  • Policy tests assert that governance rules (tagging, encryption, allowed regions) hold.

  • Unit-style tests, where the ecosystem supports them, exercise individual modules in isolation.

  • Plan inspection asserts the plan diff matches expectations, especially checking for unintended destroys.

  • Integration tests deploy into an ephemeral, disposable environment and verify real behavior.

  • Smoke tests and post-deployment verification confirm the applied infrastructure actually works as intended.

Ephemeral test environments, spun up for a pull request and destroyed afterward, catch problems that plan inspection alone misses, at the cost of extra pipeline time and cloud spend.

How to Choose the Right Infrastructure as Code Tool

Tool choice should follow the shape of the problem, not the popularity of the tool. The criteria below matter to differing degrees depending on the team.

  • Cloud scope: single-cloud versus genuinely multi-cloud requirements change which tool's ecosystem fits best.

  • Team language skills: a team fluent in TypeScript or Python may be more productive with Pulumi or CDK than learning a new DSL.

  • DSL vs. general-purpose language: a domain-specific language is often simpler to reason about; a general-purpose language brings existing test frameworks and IDE tooling.

  • Provider and ecosystem maturity: how complete and well-maintained is support for the specific services you actually use?

  • State/control model: an explicit state file, platform-tracked state, or a continuously reconciling control plane each carry different operational burdens.

  • Drift and reconciliation behavior: does the tool merely report drift, or actively reconcile it, and how often?

  • Testability: what testing layers does the ecosystem realistically support today?

  • Policy and governance needs: does the tool integrate with a policy-as-code engine your compliance requirements demand?

  • Secrets integration: how cleanly does the tool pull from your existing secrets manager?

  • CI/CD fit: does it plug into your existing pipeline tooling without excessive custom scripting?

  • Module/package ecosystem: how much of what you need already exists as a well-maintained module?

  • IDE and developer experience: autocomplete, type checking, and inline documentation affect daily velocity.

  • Licensing and governance: open-source foundation governance versus a single vendor's commercial roadmap carries different long-term risk.

  • Support model: community support, commercial support, or an enterprise SLA.

  • Managed-service availability: does a vendor offer hosted state, run history, and policy enforcement, or is that self-hosted?

  • Security requirements: does the tool support the identity, encryption, and audit features your environment demands?

  • Kubernetes dependency: Crossplane's model assumes a Kubernetes control plane is an acceptable operational dependency.

  • Platform-engineering strategy: are you building a self-service internal developer platform, where the tool becomes an implementation detail behind an API?

  • Import/migration options: how well does the tool adopt infrastructure that already exists outside it?

  • Operational burden: who maintains the state backend, the CI runners, and the upgrade cadence?

  • Long-term maintainability and total cost of ownership: licensing, compute, and the engineering time to maintain the pipeline itself.

  • Lock-in and exit strategy: how much rewrite would switching tools require in three years?

Questions to Ask Before Choosing an IaC Tool

  1. Are we primarily single-cloud or genuinely multi-cloud today, and in two years?

  2. What languages or DSLs does the team already know well?

  3. Do we want an explicit state file, or do we prefer the platform to track state for us?

  4. What are our compliance and audit requirements, concretely?

  5. How mature is provider support for the specific services we use most?

  6. What is the tool's licensing and governance model, and who controls its roadmap?

  7. What support model do we require: community, commercial, or enterprise SLA?

  8. How will this tool fit into our existing CI/CD pipeline?

  9. What would migrating existing, already-deployed infrastructure into this tool cost?

  10. What is our exit strategy if we need to switch tools later?

  11. How easy is it to test changes before they reach production?

  12. Who will own state, secrets, and pipeline maintenance day to day?

  13. Does our platform-engineering strategy require a Kubernetes-native control plane?

  14. What skills will the team need in three years, and can we hire or train for them?

Infrastructure as Code Tools Compared

The table below reflects publicly documented, current capabilities as of 2026. It is a comparison, not a ranking; no tool is labeled 'best overall'.

Tool

Authoring approach

Typical scope

State/control model

Major strengths

Key trade-offs

Terraform

HCL (declarative DSL)

Multi-cloud, broad provider ecosystem

Explicit state file/backend

Huge provider and module ecosystem; mature tooling

CLI is Business Source Licensed; IBM-owned roadmap

OpenTofu

HCL (same syntax as Terraform)

Multi-cloud, same providers as Terraform

Explicit state file/backend

MPL 2.0, Linux Foundation governance; drop-in for most Terraform configs

Younger independent feature set; some HCP-only features have no equivalent

Pulumi

General-purpose languages (TypeScript, Python, Go, .NET, Java) or YAML

Multi-cloud

Explicit state, self-managed or Pulumi Cloud

Real language tooling: tests, IDEs, loops, packages

Debugging can involve both the language runtime and the deployment engine

AWS CloudFormation

Declarative JSON/YAML templates

AWS only

AWS-tracked stacks, no separate state file

Native AWS support, no extra backend to manage

AWS-only; JSON/YAML can get verbose at scale

AWS CDK

General-purpose languages, synthesizes to CloudFormation

AWS only

Same as CloudFormation (via synthesis)

Familiar language ergonomics on top of native AWS deployment

Still bounded by CloudFormation's deployment engine and limits

Azure Bicep

Declarative DSL, transpiles to ARM JSON

Azure only

Tracked by Azure Resource Manager, no separate state file

Day-zero support for new Azure resources; simpler than raw ARM

Azure-only; no external state to inspect or migrate

Google Cloud Infrastructure Manager

Runs standard Terraform configuration ('blueprints')

Primarily Google Cloud

Terraform state, managed by the service

Managed Terraform execution with Google Cloud integration

Effectively a managed way to run Terraform, not a separate language

Crossplane

Kubernetes YAML (Compositions, XRDs)

Multi-cloud, Kubernetes-centric platforms

Live Kubernetes objects, continuously reconciled

Continuous reconciliation; strong fit for internal developer platforms

Assumes a Kubernetes control plane as an operational dependency

Ansible deserves a separate note rather than a row in this table: it is primarily a configuration-management and orchestration tool, agentless and push-based, that configures existing hosts and can call cloud APIs, but it is not built around the plan/state model that defines the provisioning tools above. Teams often use Ansible alongside a provisioning tool rather than instead of one.

Which IaC Tool Fits Which Scenario?

  • AWS-centric team: CloudFormation or CDK give native support with no separate state backend; Terraform or OpenTofu fit if multi-cloud is likely later.

  • Azure-centric team: Bicep is a natural default for pure-Azure estates; Terraform or OpenTofu fit better if other clouds are already in play.

  • Google Cloud-centric team: Infrastructure Manager offers a managed way to run Terraform against Google Cloud without self-hosting the backend.

  • Multi-cloud organization: Terraform, OpenTofu, and Pulumi all provide one workflow across providers, though 'supports multiple clouds' is not the same as 'multi-cloud architecture is simple'.

  • Team that prefers TypeScript, Python, or Go: Pulumi or AWS CDK let engineers stay in a language they already know and test with familiar frameworks.

  • Team that prefers an HCL-style declarative workflow: Terraform or OpenTofu keep the configuration close to a pure description of desired state.

  • Organization prioritizing open-source governance: OpenTofu's Linux Foundation and CNCF governance is a material difference from a single vendor's roadmap.

  • Kubernetes-native platform team: Crossplane fits naturally if the organization already treats Kubernetes as its control plane.

  • Small startup: a single well-understood tool with a low learning curve and strong documentation usually beats an elaborate multi-tool platform.

  • Enterprise with strong governance requirements: prioritize policy-as-code integration, audit trails, and a supported managed backend over raw feature count.

  • Brownfield estate requiring import: weigh each tool's import tooling and how safely it detects an unintended change on first import.

  • Team deeply invested in one provider's native ecosystem: staying with that provider's native tool (CDK, Bicep) reduces translation overhead, at the cost of portability if strategy changes later.

Common Infrastructure as Code Mistakes

  • Giant root modules that manage an entire account, making every plan slow and every review nearly impossible to reason about.

  • Manual production edits made 'just this once,' which immediately create drift and undermine the whole premise of IaC.

  • Weak state protection: unencrypted, unlocked, unbacked-up state left as a single point of failure.

  • Secrets committed to source control, even temporarily, which should be treated as a rotation event once discovered.

  • Skipping plans before production applies, especially under deadline pressure.

  • Upgrading providers or modules without testing, discovering breaking changes in production.

  • Over-abstraction: wrapping every resource in a custom module until nobody can trace what actually gets created.

  • Excessive copy-paste between environments instead of parameterized, shared modules.

  • Unclear ownership of shared modules and pipelines, so nobody notices when they rot.

  • Ignoring drift reports until they cause an incident.

  • Overly broad CI/CD credentials that can touch far more than a given pipeline should.

  • Mixing unrelated environments in one state or one pipeline run.

  • Reaching for IaC where a simpler solution would do, such as a single throwaway test resource that does not need full module treatment.

Adopting IaC: A Practical Roadmap

  1. Inventory existing infrastructure, including anything created manually.

  2. Pick a bounded, low-risk pilot rather than the whole estate at once.

  3. Select tooling based on the criteria above, not on popularity alone.

  4. Establish repository conventions: structure, naming, and review requirements.

  5. Design state and secrets management before writing the first module.

  6. Build CI validation: format, lint, and security checks on every pull request.

  7. Import or recreate the pilot's infrastructure, verifying the first plan shows no unintended changes.

  8. Add human review and, where warranted, policy-as-code approval gates.

  9. Layer in testing, security scanning, and policy checks incrementally.

  10. Expand into reusable, well-documented modules as patterns repeat.

  11. Add scheduled drift detection once the pilot is stable.

  12. Measure outcomes (lead time, incident rate, review time) and refine the process before scaling further.

Greenfield environments can adopt IaC from day one with relatively little friction. Brownfield environments need an explicit import phase: bring resources under management deliberately, one at a time or in small batches, and treat any unexpected diff in the first plan after import as a signal to investigate, not to override.

The Future of Infrastructure as Code

Several themes are well-established rather than speculative. Platform engineering and internal developer platforms increasingly put IaC behind a self-service API, so application teams request 'a standard database' without touching Terraform or Crossplane directly. GitOps and continuous reconciliation are extending beyond application manifests into infrastructure itself, an area where Crossplane's model already sits. Policy as code continues to move governance checks earlier, into the pull request rather than after deployment.

More speculatively, AI-assisted authoring is emerging as a productivity aid for writing and reviewing configuration, and the open-source governance question raised by Terraform's relicensing (and OpenTofu's response to it) is likely to keep shaping which ecosystems organizations trust with long-term infrastructure investments. These are directions worth watching, not settled outcomes.

FAQ

What is Infrastructure as Code in simple terms?

Infrastructure as Code is writing down what servers, networks, and cloud services you want, in a text file, and using a tool to create them automatically. Instead of clicking through a console, you commit a file, and the tool reads it and builds the matching infrastructure.

Is Terraform the same thing as Infrastructure as Code?

No. Terraform is one IaC tool among several, including OpenTofu, Pulumi, CloudFormation, CDK, and Bicep. IaC is the broader practice of managing infrastructure through code; Terraform is a popular implementation of that practice.

Is Infrastructure as Code only for the cloud?

No. IaC applies to on-premises virtualization and private cloud platforms too, using tools with the appropriate providers. It is most associated with public cloud because API-driven, elastic infrastructure is where manual management breaks down fastest.

What is the difference between declarative and imperative IaC?

Declarative IaC describes the desired end state and lets the tool figure out how to get there; imperative IaC describes the steps to take. Many modern tools blur this line, using general-purpose languages that still compile down to a declarative deployment plan.

What is infrastructure drift?

Drift is a mismatch between what your configuration says should exist and what is actually deployed, often caused by a manual console change or an out-of-band emergency fix. Detection and reconciliation behavior for drift varies significantly by tool.

Does every IaC tool require a state file?

No. Terraform, OpenTofu, and Pulumi keep an explicit state file or store. CloudFormation and Bicep instead rely on the cloud platform itself to track deployment state, so there is no separate file to manage.

Is Infrastructure as Code secure?

IaC is not automatically secure; it reproduces whatever you define, including misconfigurations, with perfect consistency. Security comes from practices layered on top: secrets management, least privilege, scanning, and policy-as-code checks.

Terraform vs OpenTofu: what is the difference?

OpenTofu is a Linux Foundation-governed, MPL 2.0-licensed fork of Terraform created after HashiCorp moved Terraform's CLI to the Business Source License in 2023. Most existing Terraform configurations run on OpenTofu largely unchanged, though the two have since added some independent features.

Is Ansible an IaC tool?

Ansible is best understood as a configuration-management and automation tool rather than a provisioning-focused IaC tool: it excels at configuring hosts that already exist and can call cloud APIs, but it does not center on the plan/state model that Terraform-style tools use.

Can existing infrastructure be imported into IaC?

Yes, most major tools support importing resources that were created manually, though the process and safety guarantees vary. After importing, always review the first generated plan carefully for unintended changes before applying anything.

Key Takeaways

  • IaC is a practice, not a single product; several tools implement it in meaningfully different ways.

  • Declarative and imperative are not a clean binary; general-purpose-language tools blend the two.

  • State is not universal: some tools track it explicitly, others lean on the platform, and Crossplane reconciles continuously.

  • Drift, secrets exposure, and destructive changes are the risks that matter most operationally, and none are solved by tool choice alone.

  • Configuration management, GitOps, and containers overlap with IaC but are not interchangeable with it.

  • Benefits like auditability and repeatability depend on disciplined review and testing, not on adoption alone.

  • No IaC tool is a universal winner; fit depends on cloud scope, team skills, governance needs, and long-term maintainability.

  • Terraform's licensing change and OpenTofu's fork are a live example of why governance matters as much as features.

Actionable Next Steps

  1. Inventory your current infrastructure and flag anything created or changed manually in the last quarter.

  2. Pick one bounded, low-risk system as a pilot rather than committing to a full migration up front.

  3. Score two or three candidate tools against the criteria and questions in the tool-selection section above.

  4. Stand up version control, a remote state backend (if applicable), and a secrets manager before writing the pilot's first module.

  5. Add CI validation, linting, and a required plan-review step before the pilot's first production apply.

  6. Run the pilot for a full change cycle, then measure lead time, review friction, and incident rate before scaling further.

Glossary

  • Infrastructure as Code — managing infrastructure through machine-readable configuration instead of manual steps.

  • Desired state — the infrastructure described in configuration; what should exist.

  • Actual state — what is really deployed right now.

  • Declarative — describing the end state and letting a tool determine the steps.

  • Imperative — describing the steps to take directly.

  • Provider — a plugin that lets a tool call a specific platform's API.

  • Resource — a single managed infrastructure object.

  • Module / component — a reusable, parameterized bundle of resources.

  • State — a tool's record of what it believes is deployed.

  • Backend — where state is stored.

  • Locking — preventing concurrent applies from corrupting shared state.

  • Drift — a mismatch between desired configuration and actual deployment.

  • Idempotency — repeated runs converge on the same result.

  • Reconciliation — bringing actual state back in line with desired state.

  • Plan / preview — a computed diff shown before applying changes.

  • Policy as code — machine-checked governance rules evaluated automatically.

  • Configuration management — tools that configure hosts that already exist.

  • GitOps — an operating model where Git is the source of truth and a controller reconciles continuously.

  • Immutable infrastructure — replacing resources rather than modifying them in place.

  • CI/CD — automated pipelines that build, test, and deploy changes.

  • Secret — a credential or sensitive value that must not be exposed in code or plaintext state.

  • Dependency graph — the ordering relationships a tool uses to sequence changes safely.

  • Control plane — the system, such as Kubernetes in Crossplane's model, that continuously manages desired state.

Sources & References

bottom of page