| Tool | Version | Install |
|---|---|---|
| Docker | 24+ | https://docs.docker.com/get-docker |
| AWS CLI | 2.x | https://aws.amazon.com/cli |
| Terraform | >= 1.5.0 | https://developer.hashicorp.com/terraform |
| kubectl | 1.32+ | https://kubernetes.io/docs/tasks/tools |
| Helm | 3.x | https://helm.sh/docs/intro/install |
The Terraform and AWS CLI commands in this guide use the production AWS profile.
Configure it once:
aws configure --profile productionYou will be prompted for:
- AWS Access Key ID
- AWS Secret Access Key
- Default region:
us-east-1 - Default output format:
json
Verify it works:
aws sts get-caller-identity --profile productionFrom the repo root:
cd color-app
docker build -t color-app:latest .Tag for a specific version:
docker tag color-app:latest color-app:v1.0docker loginEnter your Docker Hub username and password when prompted.
# Replace <dockerhub-username> with your Docker Hub username
docker tag color-app:latest <dockerhub-username>/color-app:latest
docker tag color-app:latest <dockerhub-username>/color-app:v1.0
docker push <dockerhub-username>/color-app:latest
docker push <dockerhub-username>/color-app:v1.0AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile production)
AWS_REGION=us-east-1
aws ecr get-login-password --region $AWS_REGION --profile production \
| docker login --username AWS --password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.comECR_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/color-app
docker tag color-app:latest $ECR_URI:latest
docker tag color-app:latest $ECR_URI:v1.0
docker push $ECR_URI:latest
docker push $ECR_URI:v1.0The ECR repository
color-appis created by Terraform in the next step. Runterraform applyfirst if the repository does not exist yet.
The Terraform code provisions:
| Resource | Details |
|---|---|
| VPC | Public + private subnets across 2–3 AZs, NAT gateway |
| EKS cluster | color-app-cluster-<env>, Kubernetes 1.32 |
| Managed node group | t3.medium (dev/stg) or t3.large (prod) |
| AWS Load Balancer Controller | Deployed via Helm, IRSA-backed service account |
| ECR repository | color-app — single image repository |
| CloudWatch log group | /color-app, 14-day retention |
| S3 bucket | color-app-bucket-<env>, versioned, private |
| IAM IRSA roles | EBS CSI, LB controller, app service account |
The VPC module automatically applies the correct Kubernetes subnet tags:
# Public subnets — used by internet-facing ALBs
kubernetes.io/role/elb = 1
kubernetes.io/cluster/color-app-cluster-<env> = shared
# Private subnets — used by internal ALBs and EKS nodes
kubernetes.io/role/internal-elb = 1
kubernetes.io/cluster/color-app-cluster-<env> = shared
These tags are required for the AWS Load Balancer Controller to discover subnets when creating an Ingress.
The controller is deployed automatically via helm.tf as part of terraform apply. It:
- Runs in the
kube-systemnamespace - Uses the
aws-load-balancer-controllerservice account - Is bound to the
color-app-cluster-<env>-lb-controllerIAM role via IRSA - Watches for
Ingressresources withkubernetes.io/ingress.class: alband provisions ALBs
cd terraform
terraform init# dev
terraform plan -var-file="env/dev/terraform.tfvars"
# stg
terraform plan -var-file="env/stg/terraform.tfvars"
# prod
terraform plan -var-file="env/prod/terraform.tfvars"# dev
terraform apply -var-file="env/dev/terraform.tfvars"
# prod (review plan carefully before applying)
terraform apply -var-file="env/prod/terraform.tfvars"Type yes when prompted to confirm.
cluster_name = "color-app-cluster-dev"
cluster_endpoint = "https://..."
ecr_app_url = "<account>.dkr.ecr.us-east-1.amazonaws.com/color-app"
app_bucket_name = "color-app-bucket-dev"
app_irsa_role_arn = "arn:aws:iam::<account>:role/color-app-cluster-dev-app-sa"
lb_controller_role_arn = "arn:aws:iam::<account>:role/color-app-cluster-dev-lb-controller"
kubeconfig_command = "aws eks update-kubeconfig --name color-app-cluster-dev --region us-east-1 --profile production"
Copy and run the kubeconfig_command from the Terraform output, or run:
aws eks update-kubeconfig \
--name color-app-cluster-dev \
--region us-east-1 \
--profile productionkubectl get nodes
kubectl get pods -n kube-systemkubectl get deployment aws-load-balancer-controller -n kube-system
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controllerExpected output:
NAME READY UP-TO-DATE AVAILABLE
aws-load-balancer-controller 2/2 2 2
Apply the Kubernetes manifests:
kubectl apply -f kubernetes/NAMESPACE/namespace.yaml
kubectl apply -f kubernetes/CONFIGMAP/configmap.yaml
kubectl apply -f kubernetes/SECRET/secret.yaml
kubectl apply -f kubernetes/DEPLOYMENT/deploy.yaml
kubectl apply -f kubernetes/DEPLOYMENT/lb-svc.yaml
kubectl apply -f kubernetes/INGRESS/ingress.yamlkubectl get ingress -n color-appThe ADDRESS column will show the ALB DNS name once the controller has provisioned it (usually 1–2 minutes).
kubectl describe ingress wandaprep-ingress -n color-appterraform destroy -var-file="env/dev/terraform.tfvars"Always destroy dev/stg before prod. Ensure no PVCs or load balancers created by Kubernetes remain, as Terraform cannot delete AWS resources it did not create.
| Symptom | Resolution |
|---|---|
NoCredentialProviders |
Run aws configure --profile production and verify with aws sts get-caller-identity |
| ECR login fails | Ensure the IAM user/role has ecr:GetAuthorizationToken permission |
| Ingress stuck with no ADDRESS | Check LB controller logs: kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller |
| Subnet not found by LB controller | Confirm subnet tags include kubernetes.io/role/elb=1 and the correct cluster tag |
kubectl returns Unauthorized |
Re-run aws eks update-kubeconfig with --profile production |
| Nodes not joining cluster | Check node group IAM role has AmazonEKSWorkerNodePolicy and AmazonEKS_CNI_Policy |