* docs: bring doc titles and descriptions into SEO length limits Rewrite the frontmatter title and description of every published doc so the rendered <title> lands in 50-60 characters (including the site suffix) and the meta description lands in 150-160 characters. * docs: move SEO titles to a seoTitle field and restore display titles The frontmatter title renders as the page H1 and the sidebar label, so tuning it for the title tag shortened nothing and lengthened the visible navigation. Put the search-facing string in seoTitle and give title back its original value.
title, seoTitle, description, category, tags, resources
| title | seoTitle | description | category | tags | resources | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| EKS Quickstart | Terraform EKS Quickstart on Spinifex — Spinifex Docs | Stand up a minimal managed Kubernetes cluster with Terraform: a VPC, IAM roles, an EKS cluster, a worker node group, an ECR repository, and a demo web app. | Terraform Workbooks |
|
|
Terraform: EKS Quickstart
The smallest end-to-end EKS example on Spinifex: a VPC, the two IAM roles EKS needs, a one- or three-worker cluster, an ECR repository, and a Spinifex-themed demo app you can open in a browser.
Table of Contents
Overview
Provision a managed Kubernetes cluster on Spinifex with Terraform/OpenTofu — and finish with something you can actually see. As well as the cluster, this workbook creates an ECR repository, then deploys the Spinifex-themed demo app onto the cluster and publishes it on the worker's public IP. When apply finishes, open the demo_url output: the page reports the pod, node, cluster, and region that answered. Refresh it and the answering pod changes, so even a non-technical viewer can watch Kubernetes scheduling and load-balancing in real time.
Under the hood it keeps things minimal: a VPC with two public subnets, an IAM role for the control plane, an IAM role for the workers with the three managed policies AWS-managed node groups expect, a public-endpoint aws_eks_cluster, and a node group sized by node_desired_size (1 for a single-node demo, or 3 for an HA-shaped cluster). You build the demo image and push it to the ECR repository, then Terraform uses the Kubernetes provider (authenticating exactly like kubectl does, via aws eks get-token) to deploy the Deployment + NodePort Service.
What you'll learn:
- Configuring the AWS provider to target Spinifex's
eks,ec2,iamandstsendpoints - Creating the EKS cluster and worker IAM roles with faithful managed-policy attachments
- Provisioning an
aws_eks_clusterand a managedaws_eks_node_group - Pointing the Kubernetes provider at the new cluster and deploying a workload from the same
apply - Opening one rule on the auto-managed worker security group to expose a NodePort
What gets created
| Resource | Name | Purpose |
|---|---|---|
| VPC | eks-quickstart-vpc |
Isolated network (10.30.0.0/16) |
| Subnets | eks-quickstart-subnet-a/-b |
Public subnets for the cluster and worker |
| Internet Gateway | eks-quickstart-igw |
Egress so the worker can pull the demo image |
| IAM Roles | eks-quickstart-cluster-role, eks-quickstart-node-role |
Control-plane and worker roles |
| ECR Repository | spinifex-demo |
Holds the demo image the workers pull |
| EKS Cluster | eks-quickstart |
Public API endpoint, API auth mode, Kubernetes 1.32 |
| Node Group | default |
node_desired_size t3.medium worker(s) — 1 or 3 |
| SG Ingress Rule | eks-quickstart-demo-nodeport |
Opens the demo NodePort on the auto-managed worker SG |
| K8s Deployment | spinifex-demo |
The themed demo image from ECR, 2 replicas |
| K8s Service | spinifex-demo |
NodePort publishing the demo on the worker |
Spinifex specifics
- The cluster's security groups are auto-managed —
vpc_config.security_group_idsis ignored. To reach a NodePort, this workbook looks the worker SG up by its deterministic name (eks-cluster-<name>-nodegroup-sg) and adds a single ingress rule. - The worker AMI is always Spinifex's
eks-nodeimage;ami_typeis recorded but does not select the image. authentication_modemust be"API"(theAPI_AND_CONFIG_MAPmode is rejected).- The workers need outbound internet (here via the IGW) to pull the demo container image.
Prerequisites:
- Spinifex installed and running (see Installing Spinifex)
- The Spinifex
eks-nodeimage available on the cluster - OpenTofu or Terraform, plus
kubectland the AWS CLI - Docker, to build and push the demo image to ECR
Instructions
Step 1. Get the Template
git clone --depth 1 --filter=blob:none --sparse https://github.com/mulgadc/spinifex.git spinifex-tf
cd spinifex-tf
git sparse-checkout set docs/terraform-workbooks
cd docs/terraform-workbooks/eks-quickstart
Or create a main.tf file and paste the full configuration below.
Step 2. Deploy
The cluster and the demo app are two root modules with separate state. Apply the cluster first:
export AWS_PROFILE=spinifex
tofu init
tofu apply
This creates the cluster (which bootstraps a control-plane VM and brings up k3s — a few minutes in CREATING), launches the worker(s), creates the spinifex-demo ECR repository, and opens the NodePort.
Set the worker count with node_desired_size (1 for a single node, 3 for an HA-shaped cluster):
tofu apply -var node_desired_size=3
Once the cluster is ACTIVE, build and push the demo image to the ECR repository this created (full commands in ../demo-app/README.md):
cd ../demo-app
REGISTRY=$(cd ../eks-quickstart && tofu output -raw ecr_repository_url)
REGISTRY_HOST=${REGISTRY%%/*}
aws ecr get-login-password | docker login --username AWS --password-stdin "$REGISTRY_HOST"
docker build -t "${REGISTRY}:latest" .
docker push "${REGISTRY}:latest"
cd ../eks-quickstart
Then deploy the demo app from the nested workloads/ module — it defaults demo_image to the parent's ECR repository at :latest:
cd workloads
tofu init
tofu apply
Why two modules? The Kubernetes provider in
workloads/reads the cluster endpoint from a livedata "aws_eks_cluster"source, so it's only ever configured while the cluster exists. Keeping it out of the cluster module meansdestroynever tries to refresh a workload against a cluster that's already gone — the failure mode where the provider falls back tohttp://localhost:80and reportsconnection refused. Always destroyworkloads/before the cluster.
Same profile for the Kubernetes provider. The Kubernetes provider authenticates by shelling out to
aws eks get-token, which has to reach the Spinifex STS endpoint. KeepAWS_PROFILE=spinifexexported for both applies.
Step 3. Open the Demo
Run from the cluster module directory (cd .. if you're still in workloads/):
tofu output demo_url
Open that URL in a browser. You'll see the Spinifex-themed page reporting the pod, node, cluster, and region that handled the request. Refresh a few times — with two replicas, the pod name alternates, demonstrating that the Service is load-balancing across the cluster.
Step 4. Inspect with kubectl (optional)
aws eks update-kubeconfig --name eks-quickstart --region ap-southeast-2
kubectl get nodes
kubectl get pods -o wide
kubectl get pods -o wide shows the demo pods and which node each landed on.
Cleanup
Destroy in reverse — the demo app first (while the cluster is still up), then the cluster:
cd workloads
tofu destroy
cd ..
tofu destroy
Troubleshooting
Demo URL Doesn't Load
The page is served from a NodePort on the worker's public IP. Work through the chain:
# Is the worker running with a public IP?
aws ec2 describe-instances --filters "Name=tag:spinifex:eks-cluster,Values=eks-quickstart" \
--query 'Reservations[].Instances[].[InstanceId,State.Name,PublicIpAddress]' --output text
# Did the demo pods roll out?
kubectl get pods -o wide
# Is the NodePort rule on the worker SG?
aws ec2 describe-security-groups --filters "Name=group-name,Values=eks-cluster-eks-quickstart-nodegroup-sg" \
--query 'SecurityGroups[0].IpPermissions'
If the pods are Pending, the worker may not be Ready yet — give it a moment. If they're ImagePullBackOff, the worker can't pull from ECR: confirm you built and pushed the image (tofu output ecr_repository_url), that the IGW route and the worker's public IP are in place, and that the node role carries AmazonEC2ContainerRegistryReadOnly.
kubernetes provider: connection refused / Unauthorized
The provider runs aws eks get-token against the Spinifex STS endpoint. Confirm the AWS CLI is still pointed at Spinifex and the cluster is ACTIVE:
aws sts get-caller-identity
aws eks describe-cluster --name eks-quickstart --query 'cluster.status'
If the cluster was still CREATING when the provider first tried to connect, just re-run tofu apply.
Cluster Stuck in CREATING
Control-plane bootstrap takes a few minutes. Confirm the underlying VM came up:
aws eks describe-cluster --name eks-quickstart --query 'cluster.status'
aws ec2 describe-instances --profile spinifex
Provider Connection Refused
sudo systemctl status spinifex.target
curl -k https://localhost:9999/