3 min read
On this page

Infrastructure as Code

IaC Principles

Infrastructure as Code (IaC) manages infrastructure through machine-readable definition files rather than manual processes.

Core Properties

| Principle | Description | Benefit | |-----------|-------------|---------| | Declarative | Define desired state, not steps | Engine determines how to reach state | | Idempotent | Applying same config repeatedly yields same result | Safe to re-run after failures | | Versioned | Stored in version control | Audit trail, rollback, code review | | Reproducible | Same config produces identical infrastructure | Consistent environments | | Self-documenting | Code is the documentation | No config drift from undocumented changes |

Declarative vs Imperative

Declarative (Terraform, CloudFormation):     Imperative (scripts, Pulumi):
"I want 3 servers with 4 GB RAM"             "Create server A, then B, then C"
  → Engine figures out the plan                 → You specify every step
  → Handles create/update/delete                → You handle state transitions
  → Converges to desired state                  → You manage ordering

Terraform

Terraform is the most widely adopted multi-cloud IaC tool, using HashiCorp Configuration Language (HCL).

Architecture

┌───────────┐     ┌───────────┐     ┌───────────────┐
│  HCL Code │────►│ Terraform │────►│   Providers   │
│  (.tf)    │     │   Core    │     │ ┌───────────┐ │
└───────────┘     │           │     │ │ AWS       │ │
┌───────────┐     │  Plan ──► │     │ │ GCP       │ │
│  State    │◄───►│  Apply    │     │ │ Azure     │ │
│  (.tfstate)│    │  Destroy  │     │ │ Kubernetes│ │
└───────────┘     └───────────┘     │ │ Datadog   │ │
                                    │ └───────────┘ │
                                    └───────────────┘

Providers

Providers are plugins that interface with APIs of cloud platforms and services.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

Resource Configuration

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = { Name = "${var.project}-vpc" }
}

resource "aws_subnet" "private" {
  count             = length(var.azs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  availability_zone = var.azs[count.index]
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.private[0].id

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = false
  }
}

Modules

Modules are reusable, encapsulated infrastructure components.

# Using a module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "production"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = var.environment != "production"
}

State Management

Terraform state maps real-world resources to configuration.

# Remote state backend (required for team collaboration)
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "prod/networking/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"   # State locking
    encrypt        = true
  }
}

State best practices:

  • Always use remote backends (S3, GCS, Azure Blob, Terraform Cloud)
  • Enable state locking to prevent concurrent modifications
  • Use separate state files per environment and component
  • Never commit state files to version control (contains secrets)
  • Use terraform import for existing resources

Terraform Workflow

terraform init      → Download providers and modules
terraform plan      → Preview changes (diff current vs desired)
terraform apply     → Execute the plan
terraform destroy   → Remove all managed resources

terraform fmt       → Format code consistently
terraform validate  → Check syntax and internal consistency
terraform state     → Inspect and manipulate state

Pulumi

Pulumi uses general-purpose programming languages instead of DSLs.

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

const bucket = new aws.s3.Bucket("my-bucket", {
  versioning: { enabled: true },
  serverSideEncryptionConfiguration: {
    rule: {
      applyServerSideEncryptionByDefault: {
        sseAlgorithm: "aws:kms",
      },
    },
  },
});

// Use real programming constructs
const topics = ["orders", "payments", "inventory"];
const snsTopics = topics.map(name =>
  new aws.sns.Topic(name, { name: `${stack}-${name}` })
);

export const bucketName = bucket.id;

Advantages over Terraform: Familiar languages (TypeScript, Python, Go, C#), real conditionals/loops, IDE support, unit testing with standard frameworks.

AWS CDK

AWS CDK generates CloudFormation templates from high-level constructs.

Construct Levels

L1 (Cfn): Direct CloudFormation resources   → CfnBucket
L2 (Curated): Sensible defaults + helpers   → Bucket (encryption, versioning)
L3 (Patterns): Multi-resource architectures → LambdaRestApi (API GW + Lambda + IAM)
import { Stack, RemovalPolicy } from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3n from 'aws-cdk-lib/aws-s3-notifications';

const bucket = new s3.Bucket(this, 'DataBucket', {
  encryption: s3.BucketEncryption.S3_MANAGED,
  versioned: true,
  removalPolicy: RemovalPolicy.RETAIN,
  lifecycleRules: [{
    transitions: [{ storageClass: s3.StorageClass.GLACIER, transitionAfter: Duration.days(90) }],
  }],
});

const processor = new lambda.Function(this, 'Processor', {
  runtime: lambda.Runtime.PYTHON_3_12,
  handler: 'index.handler',
  code: lambda.Code.fromAsset('lambda/'),
});

bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(processor));
bucket.grantRead(processor);  // Automatically creates IAM policy

CloudFormation

AWS-native IaC using JSON/YAML templates. CDK and SAM compile down to CloudFormation.

  • Stacks: Unit of deployment, creates/updates/deletes as a group
  • Change sets: Preview changes before applying
  • Drift detection: Identify manual changes outside IaC
  • StackSets: Deploy across multiple accounts and regions

Crossplane

Crossplane extends Kubernetes to manage cloud infrastructure using the Kubernetes API.

apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
  name: production-db
spec:
  forProvider:
    region: us-east-1
    dbInstanceClass: db.r6g.xlarge
    engine: postgres
    engineVersion: "15"
    masterUsername: admin
    allocatedStorage: 100
  writeConnectionSecretToRef:
    name: db-credentials
    namespace: production
  • Manages infrastructure using kubectl and Kubernetes reconciliation loops
  • Compositions group multiple resources into reusable platform APIs
  • Native integration with Kubernetes RBAC and namespaces

GitOps

GitOps uses Git as the single source of truth for infrastructure and application state.

ArgoCD

Git Repository                    ArgoCD                     Kubernetes
┌─────────────┐    Sync          ┌──────────┐               ┌──────────┐
│ manifests/  │◄── monitors ────►│ ArgoCD   │── applies ───►│ Cluster  │
│ kustomize/  │    for changes   │ Server   │               │          │
│ helm/       │                  │          │◄── reports ───┤          │
└─────────────┘                  └──────────┘   status      └──────────┘

Flux

  • Lightweight GitOps operator, CNCF graduated project
  • Source controllers pull from Git, Helm, S3, OCI
  • Kustomization controller applies manifests with dependency ordering
  • Supports multi-tenancy with namespaced access control

GitOps Principles

  1. Declarative: Entire system described declaratively in Git
  2. Versioned: Git history provides audit trail and rollback
  3. Automated: Approved changes are applied automatically
  4. Reconciled: Agents continuously correct drift from desired state

Policy as Code

Open Policy Agent (OPA)

OPA evaluates policies written in Rego against structured data.

# Deny resources without required tags
package terraform.analysis

deny[msg] {
  resource := input.resource_changes[_]
  not resource.change.after.tags["Environment"]
  msg := sprintf("Resource %s missing required 'Environment' tag", [resource.address])
}

# Enforce encryption on S3 buckets
deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket"
  not has_encryption(resource)
  msg := sprintf("S3 bucket %s must have encryption enabled", [resource.address])
}

Policy Tools Comparison

| Tool | Integration | Language | Use Case | |------|-------------|----------|----------| | OPA/Conftest | Terraform, K8s, CI/CD | Rego | General policy evaluation | | Sentinel | Terraform Cloud/Enterprise | Sentinel | Terraform-specific governance | | Checkov | Terraform, CloudFormation, K8s | Python/YAML | Security scanning | | tfsec/trivy | Terraform | Go | Static security analysis | | Kyverno | Kubernetes-native | YAML | K8s admission control |

IaC Tool Selection

Multi-cloud required?
  └─ Yes → Team prefers DSL?
            └─ Yes → Terraform
            └─ No → Pulumi
  └─ No → AWS only?
            └─ Yes → CDK or SAM (serverless)
            └─ No → Kubernetes-centric?
                      └─ Yes → Crossplane
                      └─ No → Provider-native (CloudFormation, Deployment Manager)

Key Takeaways

  • IaC enables reproducible, version-controlled, reviewable infrastructure changes
  • Terraform is the de facto standard for multi-cloud IaC with its provider ecosystem
  • Pulumi and CDK bring general-purpose languages and IDE support to IaC
  • State management is critical; always use remote backends with locking
  • GitOps (ArgoCD, Flux) applies Git-based workflows to Kubernetes operations
  • Policy as code (OPA, Sentinel) enforces governance guardrails in CI/CD pipelines