For decades, setting up a server meant following a Word document with forty manual steps, clicking through web consoles and praying that the production environment resembled the test one. The result was the "snowflake" syndrome: every server unique, irreproducible and terrifying to touch. Infrastructure as code (IaC) breaks that cycle by describing servers, networks, load balancers and databases in version-controlled text files that a tool applies automatically and repeatably. Infrastructure stops being an artisanal craft and becomes software: reviewable, testable and rebuildable from scratch. This article explains the concepts that underpin IaC, compares the dominant tools and lands the best practices that separate a solid project from a minefield.
Declarative versus imperative: the distinction that changes everything
There are two philosophies for automating infrastructure. The imperative approach describes the steps to execute ("create a machine, then install the package, then open the port"). The declarative approach describes the desired final state ("I want three web servers with this package and this port open") and lets the tool work out which actions are needed to get there from the current state. Most modern tools are declarative, and for good reason: if you run the same declarative file twice, the result is identical because the tool only applies the differences. That property is called idempotence and is the heart of IaC. An imperative script run twice may create duplicate resources or fail; an idempotent declarative manifest always converges to the same state.
The dominant tools: Terraform, Ansible, CloudFormation and Pulumi
The ecosystem is divided among four big names with distinct purposes that should not be confused. Terraform (and its open-source fork OpenTofu) is the multi-cloud declarative provisioning tool par excellence: it uses the HCL language and a system of providers to manage resources in AWS, Azure, Google Cloud and hundreds of other services. AWS CloudFormation does the same but is tied exclusively to the Amazon ecosystem. Ansible shines in configuration management and the provisioning of software inside servers already created, with no agent required. Pulumi lets you write infrastructure in real programming languages (TypeScript, Python, Go) rather than a specific DSL. In practice, a mature architecture combines Terraform to create the base infrastructure and Ansible to configure what lives inside it.
| Tool | Approach | Language | Best for |
|---|---|---|---|
| Terraform / OpenTofu | Declarative | HCL | Multi-cloud provisioning |
| AWS CloudFormation | Declarative | YAML / JSON | 100% AWS environments |
| Ansible | Idempotent procedural | YAML (playbooks) | Server configuration, agentless |
| Pulumi | Declarative | TypeScript, Python, Go | Teams that prefer real languages |
State: the most delicate concept in Terraform
To know which differences to apply, Terraform maintains a state file (terraform.tfstate) that records the mapping between what you have declared and what really exists in the cloud. This is where the most serious problems of an IaC project concentrate. Keeping the state on an engineer's laptop is a recipe for disaster: if two people apply changes at the same time, the state gets corrupted; if the laptop is lost, Terraform loses track of what it manages. The mandatory best practice is remote state with locking: storing it in a shared backend (for example an S3 bucket with locking, or a managed backend) that prevents simultaneous executions. In addition, the state file often contains secrets in clear text (database passwords, keys), so it must be encrypted at rest and never pushed to a Git repository.
Workflow: plan, review and apply in CI/CD
The great advantage of treating infrastructure as code is that it inherits the software development workflow. The recommended cycle is: write the change in a branch, open a pull request, automatically run terraform plan so the team can review exactly which resources are going to be created, modified or destroyed before approving, and only after approval run terraform apply from a continuous-integration pipeline. This discipline brings three guarantees that manual clicking never did: peer review of every infrastructure change, full traceability (every modification has an author, a date and a reason in the Git history) and the ability to revert a damaging change by going back to a previous version of the code.
Security and compliance as code
IaC does not just automate; it lets you audit security before deploying. Policy-as-code tools and IaC scanners analyse the manifests for dangerous configurations (a storage bucket open to the internet, a security group with port 22 exposed to the world, encryption disabled) and block the deployment if they break the rules. This dovetails directly with compliance frameworks: the change control and traceability that ISO/IEC 27001 requires for information security management are satisfied naturally when every infrastructure change goes through review in a pull request. And when the infrastructure processes personal data, the data protection by design principle of the GDPR (Article 25) is realised by coding encryption, network segmentation and access controls directly into the manifests, rather than adding them after the fact.
Modules, environments and the fight against drift
As the project grows, the organisation of the code becomes as important as the correctness of each resource. The recommended pattern is to encapsulate repetitive infrastructure in reusable modules: instead of copying and pasting the definition of a private network into every project, you define it once as a parameterised module and invoke it with different values. This applies the "don't repeat yourself" principle to infrastructure and drastically reduces copy-paste errors. On environments, the best practice is to keep development, staging and production with the same code but different variable files, ensuring that what is tested in staging is structurally identical to what will run in production. That environment parity eliminates the classic excuse of "it worked in my environment."
The silent enemy of any mature project is drift: the divergence between what the code says and what really exists in the cloud, caused by manual changes made "just this once" in an emergency. Drift is dangerous because the next apply may undo that emergency patch without warning, or fail confusingly. The defence is twofold: on the one hand, periodically running automated drift detection that compares state and reality and alerts you to the differences; on the other, a non-negotiable cultural discipline that every change goes through the code, no exceptions. When an emergency forces a manual change, the rule is to reflect it in the code immediately before closing the incident, so the repository remains the single source of truth for the infrastructure.
Common mistakes that wreck an IaC project
- Local or unlocked state: the source of 80% of disasters. State must be remote, encrypted and with concurrency locking.
- Manual changes outside the code (drift): touching a resource by hand in the web console breaks the correspondence with the code; the next
applymay undo that fix or fail. - Secrets in the repository: putting passwords or keys in the
.tffiles or in version-controlled state. You must use a secrets manager. - A single monolithic state: putting all the infrastructure into one state makes every change slow and risky. It is best to segment by environment and by domain.
- Applying without reviewing the
plan: approving blindly is the equivalent of clicking in production without looking.
Frequently asked questions
What is the difference between Terraform and Ansible?
Terraform provisions the infrastructure (creates servers, networks, databases) declaratively and across multiple clouds. Ansible configures what lives inside those servers (installs packages, tunes services). They complement each other: Terraform builds the house, Ansible furnishes it.
What is idempotence and why does it matter?
It is the property whereby applying the same manifest several times always produces the same final state, without duplicating resources or breaking anything. It is what makes IaC predictable and safe to re-run.
Where should the Terraform state file be kept?
In a shared remote backend with concurrency locking and encryption at rest (for example a bucket with locking or a managed backend). Never on an engineer's laptop or in the Git repository, because it usually contains secrets.
Does IaC help with ISO 27001 or GDPR compliance?
Yes. The reviewable, traceable change control it provides fits the requirements of ISO/IEC 27001, and coding encryption, segmentation and access control into the manifests realises the data-protection-by-design principle of Article 25 of the GDPR.
Conclusion: rebuildable infrastructure is resilient infrastructure
The acid test of a well-built IaC project is brutally simple: if an entire region of your cloud provider were lost tomorrow, could you rebuild your whole environment from the code within hours, without anyone having to recall from memory what was configured by hand? When the answer is yes, you have eliminated the snowflake syndrome and turned disaster recovery from an improvised nightmare into an executable procedure. At Summum Systems we champion IaC not out of fashion, but because it transforms infrastructure into an asset that is reviewed as code, audited as code and rebuilt as code. Remote, locked state, the plan-review-apply workflow in CI/CD and security coded in from the design are not ornaments: they are what turns a pile of fragile servers into a platform your team dares to change without fear. And a team that does not fear its own infrastructure is a team that delivers faster and sleeps better.