Infrastructure Automation Basics
10 examples to get you started with Infra Automation - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Infra Automation - 7 basic and 3 intermediate.
uv venv && source .venv/bin/activate
uv pip install "ansible>=10.0" "pulumi>=3.0" "cdktf>=0.20"AWS_PROFILE or IAM role for cloud examples (optional for local-only runs).Infrastructure automation starts by describing what should exist, not how to click through a console.
# desired_state.py
from dataclasses import dataclass
@dataclass(frozen=True)
class BucketSpec:
name: str
versioning: bool = True
DESIRED = BucketSpec(name="app-logs-prod", versioning=True)Related: Configuration Management - templating and environments
Run the same script twice and end in the same state - no duplicate resources.
import boto3
def ensure_bucket(s3, name: str) -> None:
buckets = [b["Name"] for b in s3.list_buckets().get("Buckets", [])]
if name not in buckets:
s3.create_bucket(Bucket=name)
s3.put_bucket_versioning(
Bucket=name,
VersioningConfiguration={"Status": "Enabled"},
)
session = boto3.Session()
ensure_bucket(session.client("s3"), "demo-idempotent-bucket")create_bucket fails on the second run.put_bucket_versioning is naturally idempotent - safe to call every time.created vs updated) for logging and audits.Related: Provisioning Cloud Resources - repeatable provisioning
Ansible modules converge toward declared state automatically.
ansible localhost -m ansible.builtin.file -a "path=/tmp/iac-demo state=directory mode=0755"
ansible localhost -m ansible.builtin.file -a "path=/tmp/iac-demo state=directory mode=0755"ok with changed=0 - that is idempotency in action.file, copy, apt) over raw shell unless no module exists.-C (check mode) to preview changes without applying them.Related: Ansible - playbooks, inventories, and roles
Store infra code in git with the same rigor as application code.
git init infra-demo && cd infra-demo
mkdir -p stacks/prod
echo 'name = "prod"' > stacks/prod/Pulumi.yaml
git add . && git commit -m "chore: initial prod stack"infra-v2026.07.09) when promoting to production.Related: Pulumi (Python IaC) - real Python for cloud resources
Keep dev, staging, and prod configs distinct but structurally identical.
from dataclasses import dataclass
@dataclass(frozen=True)
class EnvConfig:
name: str
bucket_suffix: str
enable_deletion_protection: bool
ENVS = {
"dev": EnvConfig("dev", "-dev", False),
"prod": EnvConfig("prod", "-prod", True),
}deletion_protection=True) belong in config, not comments.ENVIRONMENT variable or CLI flag, never hardcode prod.Related: Configuration Management - secrets and templating
Preview changes before mutating shared infrastructure.
cd stacks/prod
pulumi preview # or: terraform plandelete on stateful resources.Related: Testing Infrastructure Code - dry-runs and policy checks
Log what changed, who ran it, and the outcome - not just success/failure.
import json
import logging
logging.basicConfig(level=logging.INFO, format="%(message)s")
log = logging.getLogger("iac")
def apply(spec: dict) -> dict:
result = {"resource": spec["name"], "action": "updated", "changed": True}
log.info(json.dumps({"event": "iac_apply", **result}))
return result
apply({"name": "app-logs-prod"})Related: Infrastructure Automation Best Practices - auditable infra rules
Compare live AWS state against your declared spec.
import boto3
def detect_versioning_drift(bucket: str, expected: str = "Enabled") -> bool:
s3 = boto3.client("s3")
resp = s3.get_bucket_versioning(Bucket=bucket)
actual = resp.get("Status", "Suspended")
return actual != expected
if detect_versioning_drift("app-logs-prod"):
raise SystemExit("drift detected: versioning mismatch")Related: Provisioning Cloud Resources - reviewable infrastructure
Generate Terraform JSON from typed Python constructs.
from cdktf import App, TerraformStack
from constructs import Construct
class MiniStack(TerraformStack):
def __init__(self, scope: Construct, ns: str):
super().__init__(scope, ns)
# Add providers and resources here - cdktf synth writes terraform JSON
app = App()
MiniStack(app, "mini")
app.synth()cdktf synth output is still plain Terraform - ops teams can inspect it.Related: Terraform with Python - CDKTF and wrapping Terraform
Reject non-compliant plans in CI before they touch cloud accounts.
def validate_tags(tags: dict) -> list[str]:
required = {"Environment", "Owner", "CostCenter"}
missing = required - set(tags)
return [f"missing tag: {t}" for t in sorted(missing)]
errors = validate_tags({"Environment": "prod"})
assert errors, "policy should fail without Owner and CostCenter"ruff and pytest.Related: Testing Infrastructure Code - linting and policy checks
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026