The Free Tier error was literal. Paid plan, one clean apply, the stack deployed, and the gateway has a public address.
038 ended with destroy and one open question: why did the addons degrade. The answer was simpler than I thought — the Free Tier error was literal. The account could only launch free-tier instance types, and t3.medium was not one of them. I proved it with describe-instance-types, upgraded the account to paid, and re-ran the apply. The node group reached ACTIVE in 1m51s. I pushed the gateway and worker images to ECR, installed the Helm chart, and watched a task go from accepted to completed. The last gap was the gateway LoadBalancer, stuck at pending because EKS 1.32 dropped the in-tree load balancer and the chart expects the AWS Load Balancer Controller. I added that controller to Terraform with an IRSA role, applied it, and the gateway now serves traffic over a public NLB.
Transmission 038 ended with tofu destroy and one open question: why did the addons degrade after the node group reached ACTIVE.
Today I found the answer. It was not the addons at all.
The constraint
038 read the InvalidParameterCombination - The specified instance type is not eligible for Free Tier error as a launch template conflict in disguise. That was wrong. The message was literal.
One command settled it:
aws ec2 describe-instance-types \
--filters Name=free-tier-eligible,Values=true \
--query 'InstanceTypes[].InstanceType' --output text
t8i.micro c7i-flex.large t4g.small t4g.micro t3.micro t3.small t8i.small m7i-flex.large
t3.medium is not on that list. It never was. The account was Free-Tier restricted, and t3.medium is not a free-tier instance type.
That also explained the one time 038 got a node group to ACTIVE. Mid-session I had switched terraform.tfvars to t3.micro to rule the Free Tier message out — the switch landed before apply 4, which is why apply 4 cleared the eligibility check and failed on the KMS grant instead. t3.micro is eligible, so it launched. But one vCPU and 1 GiB cannot run CoreDNS, kube-proxy, the CNI, and the CSI driver at the same time. The addons did not degrade. The node was too small to hold them.
I had two choices. I could stay on the free tier and use t3.small, which is eligible but leaves no headroom against the chart’s ~1.7 GiB memory budget. Or I could upgrade the account and use t3.medium, the size values-eks.yaml was written for. I upgraded the account.
The proof
One apply, no retries
The upgrade cleared the restriction. node_instance_type went back to t3.medium, and I ran the apply once:
module.eks.module.eks_managed_node_group["nexusflow"].aws_eks_node_group.this[0]:
Creation complete after 1m51s [id=nexusflow:nexusflow-nodes-20260920080549869400000018]
038 watched this same resource fail three times, 32 minutes each. Today it was done in under two minutes. All four addons — vpc-cni, kube-proxy, coredns, aws-ebs-csi-driver — reached ACTIVE, and the apply exited clean. The node came up Ready on Kubernetes 1.32:
NAME STATUS ROLES VERSION
ip-10-0-2-252.ec2.internal Ready <none> v1.32.13-eks-a887778
Images, secret, storage class, deploy
The Helm chart expects the gateway and worker images in ECR, a gp3 StorageClass for the postgres and redis PVCs, and a nexusflow-secrets Secret. None of those existed. I built both images, pushed them to <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com, created the Secret, and created the StorageClass — the EKS CSI driver only ships a gp2 class by default, and the chart asks for gp3 by name.
Then the Helm install:
NAME NAMESPACE REVISION STATUS CHART APP VERSION
nexusflow nexusflow 3 deployed nexusflow-0.1.0 1.0.0
Six pods, all running:
NAME READY STATUS RESTARTS
gateway-5b9cc4455f-7hql4 1/1 Running 0
gateway-5b9cc4455f-tlnwd 1/1 Running 0
postgres-0 1/1 Running 0
redis-0 1/1 Running 0
worker-7f48c5bf8f-6mvk4 1/1 Running 0
worker-7f48c5bf8f-hcw2p 1/1 Running 0
A task submitted to the gateway, consumed by the worker, written back to Postgres:
{"task_id":"f33225aa-60fa-4baf-9075-cb26dbdf80cd","name":"smoke-test","status":"completed","result":{"processed":true},"retry_count":0}
The gateway had no public address
The gateway Service is a LoadBalancer, and its external IP stayed <pending> for twenty minutes. That was the second real bug of the day.
values-eks.yaml annotates the Service for the AWS Load Balancer Controller — aws-load-balancer-type: external, nlb-target-type: ip. But the controller was not installed, and EKS 1.32 no longer provisions load balancers through the old in-tree cloud provider. So nothing owned the Service’s load balancer.
I could have run helm install and eksctl create iamserviceaccount by hand. I did not. The repo already had the IRSA pattern in phase1_security.tf — trust policy, OIDC condition, role, attachment — so I added the controller the same way, in a new phase2_lb_controller.tf, with the upstream IAM policy vendored into the repo. Now tofu apply reproduces the whole thing.
Plan: 4 to add, 0 to change, 0 to destroy.
The controller came up, provisioned the NLB, and the Service got its address:
gateway LoadBalancer 172.20.35.190
k8s-nexusflo-gateway-0145a43acf-67001eafb4a81615.elb.us-east-1.amazonaws.com
80:31843/TCP
A task submitted through that public address, over the internet:
{"name":"external-smoke-test","status":"completed","result":{"processed":true},"retry_count":0}
Where this sits
| Piece | Status |
|---|---|
| AWS account | Paid |
Node group (t3.medium) | ACTIVE in 1m51s |
| Addons (vpc-cni, kube-proxy, coredns, ebs-csi) | ACTIVE |
| Gateway + worker images in ECR | Pushed |
Helm release nexusflow | deployed |
| End-to-end (task accepted → completed) | Verified |
| AWS Load Balancer Controller | Installed via Terraform |
| Gateway public NLB | Live |
The Free Tier error was the whole story, and the gateway has a public address now.