New: Consumption-based container hosting is now available.Learn more →

·

Infrastructure as Code: Deploy Reproducibly, Not by Hand

Configuring servers by hand is error-prone. With Infrastructure as Code you deploy infrastructure versioned, reproducible and automated — plus an overview of the key tools.
Infrastructure as Code: Deploy Reproducibly, Not by Hand

Anyone who has ever set up a production server "by hand" knows the problem: after three months, nobody remembers exactly what's running on it and why. Every change ends up somewhere in the head of the admin who made it back then, or at best in a wiki article that was already outdated as it was being written. Infrastructure as Code solves exactly that: instead of configuring servers manually, you describe the desired state in files — versioned, reproducible, automatically executable.

This article explains what IaC concretely means, which tools are suited to which use cases, and how teams can get started without having to overhaul everything at once.

What is Infrastructure as Code?

Infrastructure as Code (IaC) means that the configuration of servers, networks, databases and other infrastructure components is not performed manually but defined in machine-readable files. These files are treated like normal application code — they live in a Git repository, are changed via pull request, and pass through a pipeline before they reach production.

There are two fundamental approaches:

Declarative describes the target state. The tool takes care of how that state is reached. Terraform is the best-known example: you define which resources should exist, and Terraform itself works out what needs to be created, changed or deleted.

Imperative describes the steps to be executed. Ansible playbooks work this way: you write a sequence of tasks that are executed in that order.

In practice, both are usually combined. Terraform provisions the infrastructure, Ansible configures the services running on it.

The problem with manual infrastructure

Manual configuration has earned a name that describes the problem well: snowflake servers. Every server is one of a kind. Two systems that should actually be identical differ in details that nobody documented. Over time they drift apart — patches get applied to one but not the other. A library gets updated manually on one because a service wasn't running. Three months later, something works in production but not in staging.

This leads to concrete problems:

  • No reproducibility. If a server fails, you can't reliably rebuild it because nobody knows exactly what was installed on it.
  • Missing version control. Who changed what and when? With manual adjustments there's no automatic answer to that.
  • Slow onboarding. New team members have to understand what the infrastructure looks like. If that's documented nowhere, it takes time.
  • Difficult compliance evidence. Auditors want to know who made which change and when. Without version control, that's tedious.

Core principles of Infrastructure as Code

Idempotency is one of the most important principles. An IaC tool must produce the same end state regardless of whether it runs once or ten times. If a server is already in the desired state, nothing happens. This makes deployments safe and repeatable.

Single source of truth means that the code in the repository defines what the infrastructure should look like. What runs on the servers should match that code. If the actual state deviates — this is called configuration drift — that's a problem that needs to be fixed.

Version control with Git brings the familiar workflows of software development to infrastructure. Changes are submitted via pull request, reviewed and tracked. If there's a problem, you can look in the Git history to see who changed what and when, and roll back if needed.

The most important IaC tools at a glance

The market for IaC tools has grown. Here's an overview of the most widely used tools and their strengths.

Terraform

Terraform by HashiCorp is the de facto standard for cloud infrastructure provisioning. Its declarative language HCL (HashiCorp Configuration Language) is readable and well documented. Terraform manages a state that reflects the current state of the infrastructure, and for every change it computes a plan showing what will change before anything is executed.

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}

One important aspect: the Terraform state must be stored securely, in a remote backend like S3, not locally on a developer's machine.

Ansible

Ansible is particularly well suited to configuration management: installing software, rolling out configuration files, starting services. It's agentless — nothing needs to be installed on the target systems, just SSH access and Python. The playbooks are written in YAML and easy to read.

- name: Install Nginx
  hosts: webservers
  tasks:
    - name: Install the Nginx package
      apt:
        name: nginx
        state: present

Ansible is not a direct competitor to Terraform but complements it. Terraform builds the infrastructure, Ansible configures it.

Pulumi

Pulumi takes a different approach: instead of a dedicated configuration language, you can work with real programming languages — TypeScript, Python, Go or C#. This makes IaC more accessible for developers who don't want to learn a new DSL, and enables more complex logic without workarounds.

import * as aws from "@pulumi/aws";

const bucket = new aws.s3.Bucket("my-bucket", {
    acl: "private",
});

For teams already heavily invested in TypeScript or Python, Pulumi can be a more natural choice than Terraform.

Helm & Kustomize

In the Kubernetes context, Helm and Kustomize take on the role of IaC tools for workloads. Helm is a package manager for Kubernetes: you define charts with templates and values that allow different configurations for different environments. Kustomize follows a different philosophy — instead of templates, base manifests are adjusted with overlays without modifying them directly.

Neither tool is a competitor to Terraform; they cover a different layer: Terraform manages the cluster, Helm and Kustomize manage the workloads running on it.

IaC in Kubernetes environments

Kubernetes didn't invent IaC, but it made it a central part of the workflow. Kubernetes manifests are themselves already declarative: you describe the desired state, and the controller ensures it's reached.

GitOps goes one step further. With GitOps, the Git repository is the single authoritative source for the cluster state. Tools like ArgoCD or Flux watch the repository and synchronize the cluster automatically when something changes. It's a pull model: the cluster pulls changes from Git, rather than someone pushing changes in from outside.

This has practical advantages: you no longer deploy with kubectl apply from a laptop, but through a merge into the main branch. The entire deployment process is thereby auditable and reproducible.

A typical IaC workflow in practice

A realistic workflow for a team that uses IaC consistently looks roughly like this:

  1. Write the change as code. A developer adds a new database by defining a Terraform resource in a .tf file.
  2. Open a pull request. The change is submitted for review. A terraform plan runs automatically and its output is commented on the PR, so everyone can see what will change.
  3. Review and merge. After approval, it's merged into main.
  4. Automatic apply. A CI/CD pipeline runs terraform apply. In a GitOps setup for Kubernetes, ArgoCD or Flux handles the synchronization.
  5. Monitoring. After deployment, you verify that the actual state matches the desired one.

This process is the same for every infrastructure change, whether it's a new server, a firewall rule or a Kubernetes configuration.

Common mistakes and how to avoid them

Configuration drift is the most common pitfall. Someone makes a quick manual change directly on the server, "just for a moment, to get it running again." The problem: that change isn't in the code. On the next apply it gets overwritten — or worse, it persists and nobody remembers why.

The solution: consistently prevent manual changes and force all changes through the IaC workflow. This requires discipline but pays off.

Secret management is another critical point. Secrets like passwords or API keys don't belong in Git, not even encrypted as a Terraform variable in the repository. Better approaches: HashiCorp Vault, AWS Secrets Manager, or Kubernetes Secrets with an external secret operator.

Overly granular modules quickly make Terraform code hard to follow. Instead of building a separate module for every resource, work with a few well-considered abstractions and only split them up once it brings real value.

Infrastructure as Code and compliance

IaC makes compliance requirements considerably easier to meet. Every change to the infrastructure is documented in the Git log: who, what, when, why (commit message). For many audit requirements, that's enough as evidence.

Policy as Code goes one step further. Tools like Open Policy Agent (OPA) or Conftest let you define policies as code and check them automatically. Example: "all storage buckets must be encrypted" can be formulated as a policy and checked in the CI pipeline — before the code is even applied.

deny[msg] {
    resource := input.resource.aws_s3_bucket[_]
    not resource.server_side_encryption_configuration
    msg := sprintf("Bucket %v must be encrypted", [resource])
}

This shifts compliance checks to the left. Problems are caught early, not only at audit time.

Getting started for teams without IaC experience

The biggest mistake when getting into IaC is trying to migrate everything at once. That rarely works. Instead, a step-by-step approach is recommended:

Step 1: New things first. All new resources are created via IaC from the start. Existing infrastructure stays as it is for now.

Step 2: Import, don't recreate. Terraform and other tools offer import functions to bring existing resources into the state without deleting and recreating them.

Step 3: Migrate gradually. Critical, stable systems are moved into the IaC workflow step by step, once the team has gained experience.

Step 4: Establish tooling. CI/CD pipelines for Terraform, linting, automatic plan outputs in the pull request — that's not a nice-to-have but a prerequisite for working safely as a team.

Conclusion

Infrastructure as Code is not a "nice to have" but the foundation for scalable, secure and traceable infrastructure. Once infrastructure is versioned, reviewable and rolled out automatically, snowflake servers disappear, drift becomes visible and deployments become repeatable.

If you want to start pragmatically, pick a manageable resource (e.g. a network, a database or a Kubernetes cluster), establish the Git workflow (PR, plan, apply) and build out from there step by step. The most important lever is less the tool than the discipline: changes happen in code, not "just quickly" by hand.