The Philosophical Developer — Chapter 14: Code Your Infrastructure
2026-07-09 · 5 min read
Code Your Infrastructure

Last chapter we stood up the local cloud: Floci as our AWS emulator, a local registry, k3s in k3d. It works. But “works” is a state, not a guarantee.
Infrastructure as code turns states into contracts.
All code for this series lives in the local-cloud repo on GitHub.
Why OpenTofu
OpenTofu forked from Terraform when HashiCorp switched to BUSL. The code is the same. The philosophy diverged — OpenTofu stayed open source, community-governed, and committed to backward compatibility. For a project about sovereignty and local control, that alignment matters.
We’re using OpenTofu 1.12.1 with native test support — .tftest.hcl files that let us apply TDD to infrastructure the same way we do to application code.
The Problem With Infrastructure Code
Traditional IaC has a gap: you write configuration, then apply, then pray. The feedback loop is slow and destructive. Drift detection is after-the-fact. There’s no test suite for your infrastructure until something breaks in production.
OpenTofu’s native testing changes that. You can:
- Write assertions before the resources exist
- Use
planmode for unit tests (idempotent, no side effects) - Reserve
applyfor integration tests against a real target - Run the full suite locally against Floci before touching anything real
This is TDD applied to infrastructure. RED/GREEN/REFACTOR, but the red is a missing VPC and the green is a working cluster.
Module Design
Five modules, five concerns. Each tested independently before composition:
infrastructure/opentofu/
├── modules/
│ ├── storage/ # Versioned S3 bucket
│ ├── network/ # VPC + subnet
│ ├── iam/ # ECS task role + policy
│ ├── dynamodb/ # State table
│ └── ecs/ # Fargate cluster + service
└── environments/
└── local/ # Composition + integration test
Storage Module
variable "bucket_name" {
type = string
default = "local-cloud-artifacts"
}
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = { managed_by = "opentofu" }
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration { status = "Enabled" }
}
Two tests: default bucket name and custom bucket name. Both pass.
Network Module
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
}
resource "aws_subnet" "this" {
vpc_id = aws_vpc.this.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 1)
map_public_ip_on_launch = true
}
VPC with DNS enabled, public subnet with auto-assign. One test, one assertion per output.
IAM Module
resource "aws_iam_role" "this" {
name = var.role_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ecs-tasks.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
Role scoped to ECS task execution with S3 read/write and CloudWatch logs. Policy attached at the module level — consumers just get a role ARN.
DynamoDB Module
The state table. Why? Because eventually this becomes the OpenTofu remote backend. For now it proves we can provision non-compute resources through the same pipeline.
resource "aws_dynamodb_table" "this" {
name = var.table_name
billing_mode = "PAY_PER_REQUEST"
hash_key = "id"
attribute = [{ name = "id", type = "S" }]
}
ECS Module
resource "aws_ecs_task_definition" "this" {
family = var.service_name
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
}
resource "aws_ecs_service" "this" {
cluster = aws_ecs_cluster.this.id
task_definition = aws_ecs_task_definition.this.arn
launch_type = "FARGATE"
}
Fargate requires awsvpc networking and explicit subnet configuration. The module accepts a subnet ID from the network module — the dependency chain is explicit.
Composition
The local environment wires everything together:
module "network" { source = "../../modules/network" }
module "storage" { source = "../../modules/storage" }
module "iam" { source = "../../modules/iam" }
module "dynamodb" { source = "../../modules/dynamodb" }
module "ecs" { source = "../../modules/ecs"; subnet_id = module.network.subnet_id }
Five modules, one integration test. The test runs plan and asserts that every output is non-empty. If the graph is broken, the plan fails. If a dependency is missing, the assertion fails.
Test Results
=== modules/storage === 2 passed
=== modules/network === 1 passed
=== modules/iam === 1 passed
=== modules/dynamodb === 1 passed
=== modules/ecs === 1 passed
=== environments/local== 1 passed
=== All tests passed === 7 runs
Seven test runs. Zero failures. Against a real Floci instance, not a mock.
Plan Mode vs Apply
Unit tests use command = plan. This validates structure, types, and provider connectivity without provisioning. Floci resources persist across runs — apply in unit tests would cause EntityAlreadyExists on the second invocation.
This mirrors how we think about tests in application code: unit tests should be idempotent and side-effect free. Integration tests are where you actually deploy.
What This Buys Us
- Reproducible —
tofu planshows exactly what changes. No drift. - Traceable — Every resource is code. Every change is a commit.
- Testable — Assertions run before provisioning. Failures are cheap.
- Safe — Local-first execution. Floci catches errors before they hit real infrastructure.
The four pillars, applied to infrastructure.
Next
State management. Remote backends. Workspace isolation. The gap between “works locally” and “works in CI” is where real infrastructure engineering lives.
Chapter 15.
Also on LinkedIn.