Terraform infrastructure for the deployment platform. Provisions the AWS foundation that all application workloads run on top of — networking, Kubernetes cluster, container registry, and IAM roles.
platform-infra/
├── modules/
│ ├── vpc/ # Network layer — VPC, subnets, NAT gateways
│ ├── eks/ # Kubernetes cluster — EKS, node groups, OIDC, addons
│ ├── ecr/ # Container registries — one repo per service
│ └── iam/ # IAM roles — GitHub Actions, ALB controller
└── environments/
├── dev/ # Dev cluster configuration
│ ├── main.tf
│ ├── backend.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── terraform.tfvars
└── prod/ # Prod cluster configuration (same structure)
Modules contain the infrastructure logic and are never environment-specific. Environments call modules with different variable values — dev gets smaller nodes and a single NAT gateway, prod gets larger nodes and one NAT gateway per AZ for high availability.
Each module depends on outputs from the previous one. Terraform resolves this automatically — you never need to apply modules in a specific order manually.
vpc → eks → iam
↘
ecr (independent, no deps)
These steps are done once per AWS account before running Terraform.
1. Install required tools
brew install terraform awscli kubectl helm
terraform -v # should be >= 1.6
aws --version2. Configure AWS credentials
aws configure
# AWS Access Key ID: from IAM → Users → your user → Security credentials
# AWS Secret Access Key: same
# Default region: us-east-2
# Default output format: json
# verify
aws sts get-caller-identity3. Create the Terraform state backend (S3 + DynamoDB)
Terraform stores its state in S3. This bucket must exist before terraform init can run. Create it once manually:
# state bucket
aws s3api create-bucket \
--bucket aetherion-terraform-state \
--region us-east-2 \
--create-bucket-configuration LocationConstraint=us-east-2
aws s3api put-bucket-versioning \
--bucket aetherion-terraform-state \
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption \
--bucket aetherion-terraform-state \
--server-side-encryption-configuration '{
"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]
}'
# state lock table (prevents concurrent applies from corrupting state)
aws dynamodb create-table \
--table-name terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-24. Create the ALB controller IAM policy
This policy does not exist in a fresh AWS account. Create it once:
curl -o alb-policy.json \
https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json
aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://alb-policy.jsonAll commands are run from inside the environment directory, never from a module directory.
cd environments/dev
# first time only — downloads providers and modules
terraform init
# preview what will be created
terraform plan
# create the infrastructure
terraform applyAfter apply completes, configure kubectl:
aws eks update-kubeconfig \
--name aetherion-dev \
--region us-east-2
kubectl get nodes # should show your nodes as ReadyInstall the ALB Ingress Controller via Helm (once per cluster):
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--namespace kube-system \
--set clusterName=aetherion-dev \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=$(terraform output -raw alb_controller_role_arn)After terraform apply, copy these outputs into your GitHub organisation or repository variables:
terraform output github_actions_role_arn # → GHA_ROLE_ARN
terraform output ecr_registry # → ECR_REGISTRY
terraform output cluster_name # → EKS_CLUSTER_NAMEThese are the values your reusable deploy workflow reads at runtime. You only need to update them when infrastructure changes (rare).
- Add the service name to
module "ecr" { service_names = [...] }inenvironments/dev/main.tfandenvironments/prod/main.tf - Add the repo name to
module "iam" { github_repos = [...] }if it has its own app repo - Run
terraform applyin both environments - The new ECR URL appears in
terraform output ecr_urls
Each module has its own README with full input/output reference:
| Setting | Dev | Prod |
|---|---|---|
| Cluster name | aetherion-dev |
aetherion-prod |
| Node instance | t3.medium |
t3.xlarge |
| Node count | 2 desired |
4 desired |
| NAT Gateways | 1 (single AZ) |
3 (one per AZ) |
| State key | platform/dev/terraform.tfstate |
platform/prod/terraform.tfstate |
Each environment has its own isolated state file in S3:
aetherion-terraform-state/
├── platform/dev/terraform.tfstate
└── platform/prod/terraform.tfstate
This means a terraform apply in environments/dev cannot affect prod resources under any circumstances.