MAY 2026

Container Orchestration
& CI/CD on AWS EKS

Two pipelines. One cluster. Built in 72 hours.

Provisioned a production-grade Kubernetes cluster on AWS EKS with Terraform, deployed a personalized portfolio application via Helm, and automated the full deployment lifecycle with two independent CI/CD pipelines — Jenkins on main, GitHub Actions + ArgoCD on gitops. Built and running in under 72 hours.

AWS EKS Terraform Kubernetes Helm Jenkins GitHub Actions ArgoCD ECR AWS ALB HPA Docker Node.js
69 AWS Resources
2 CI/CD Pipelines
4 Max Node Scale
72h Build Time
01

Architecture

// MAIN BRANCH — Jenkins CI/CD Developer Push ──▶ GitHub (main) │ └──webhook──▶ Jenkins EC2 (t3.medium) │ ┌──────────────────┤ ▼ ▼ ▼ docker build ECR push helm upgrade │ └──────────────────────────────────────────────▶ │ // GITOPS BRANCH — GitHub Actions + ArgoCD Developer Push ──▶ GitHub (gitops) │ └──trigger──▶ GitHub Actions (ubuntu-latest) │ docker build + ECR push │ update values.yaml + commit │ ArgoCD detects change ──▶ auto-sync cluster │ ▼ AWS ALB (Terraform-managed) │ ┌───────────┴───────────┐ ▼ ▼ EKS Node (t3.small) EKS Node (t3.small) │ │ App Pod ArgoCD Pods (HPA: 1–3)

Key architectural decision: The ALB is defined as an explicit Terraform resource rather than letting the Helm ALB Ingress Controller provision it dynamically. When the controller manages the ALB, it creates resources outside of Terraform's state — which means terraform destroy fails because it can't remove the VPC while the controller-managed ALB is still holding subnets. By owning the ALB in Terraform, the full infrastructure lifecycle runs cleanly from a single command.

02

What I Built

// Application — Kubernetes Downward API

Rather than deploying a generic Hello World, I built a personalized portfolio page that pulls live Kubernetes cluster data at request time using the Downward API. The pod injects POD_NAME, NODE_NAME, and NAMESPACE as environment variables — rendering real cluster data on every page load. If the pod reschedules to a different node, the page reflects it immediately.

01
Infrastructure as Code — Terraform VPC · EKS · ALB · IAM · Jenkins

Wrote five Terraform files provisioning a complete AWS environment: custom VPC with public/private subnets across three AZs, NAT gateway, EKS cluster with managed node groups, AWS ALB with listener and target group, IAM role for the ALB controller via OIDC, dedicated IAM policy for controller permissions, pre-destroy cleanup hook, and a Jenkins EC2 instance with full bootstrapping via user_data.

terraform vpc eks alb iam oidc
02
Kubernetes Deployment — Helm HPA · Ingress · Downward API

Packaged the application as a Helm chart with templated deployment, service, ingress, HPA, and service account resources. Configured Horizontal Pod Autoscaling to scale between 1 and 3 replicas at 50% CPU or memory utilization. Added Kubernetes Downward API env vars to the deployment template so each pod exposes its own metadata to the running application.

helm kubernetes hpa ingress downward api
03
Jenkins CI/CD Pipeline 4-Stage · Build Number Tagging

Jenkins runs on a dedicated EC2 instance provisioned by Terraform. A declarative Jenkinsfile defines four stages: Checkout, Build Docker Image, Push to ECR (tagged with Jenkins build number), and Deploy to EKS via helm upgrade --install. AWS credentials and kubeconfig are injected via the Jenkins credential store using the AWS Credentials and Kubernetes CLI plugins.

jenkins groovy ecr docker helm
04
GitOps — GitHub Actions + ArgoCD Pull-Based · Self-Healing · Commit SHA Tagging

GitHub Actions handles CI on the gitops branch — building and pushing images tagged with the commit SHA, then updating values.yaml and committing back to the repo. ArgoCD watches the branch continuously and auto-syncs the cluster when it detects a change. Self-healing is enabled: if any resource drifts from what the repo declares, ArgoCD corrects it. Git is the source of truth.

github actions argocd gitops ecr helm
05
AWS Load Balancer Controller OIDC · IAM Role for Service Account

Installed the AWS Load Balancer Controller into the EKS cluster via Helm. The controller uses an IAM role bound to its Kubernetes service account via OIDC federation — no static credentials required. The role is provisioned by Terraform using the iam-role-for-service-accounts-eks module, with a dedicated IAM policy granting the specific API permissions the controller needs.

alb controller oidc irsa iam
06
Infrastructure Teardown Pre-Destroy Hook · Clean Lifecycle

A null_resource with a local-exec destroy provisioner runs automatically before Terraform removes the VPC. It updates kubeconfig, deletes all Kubernetes ingress resources, and waits 60 seconds for the controller-managed ALB to terminate — preventing the subnet dependency violations that plagued the first run. Full teardown runs from a single terraform destroy.

terraform null_resource destroy hooks lifecycle
03

Problems I Solved

ISSUE Node group CREATE_FAILED — nodes joining cluster in NotReady state. The vpc-cni addon wasn't installed before nodes were provisioned, causing the aws-node pod to crash loop with ec2:DescribeNetworkInterfaces permission errors.
RESOLVED  Added before_compute = true to the vpc-cni addon block, forcing addon installation before node provisioning. Also added AmazonEKS_CNI_Policy to iam_role_additional_policies in the node group so nodes have the required EC2 API permissions from the start.
ISSUE terraform destroy failed with DependencyViolation on the first run. The ALB Ingress Controller had created an ALB dynamically — outside of Terraform's state — which was still holding subnets and the internet gateway when Terraform tried to tear down the VPC.
RESOLVED  Moved the ALB into Terraform as explicit resources: aws_lb, aws_security_group, aws_lb_listener, and aws_lb_target_group. Added a null_resource pre-destroy hook that deletes Kubernetes ingresses and waits 60 seconds before VPC teardown. Full lifecycle now runs clean.
ISSUE ALB controller v3.3.0 blocked with repeated AccessDenied on elasticloadbalancing:DescribeListenerAttributes. Ingress address never populated. This API call isn't covered by ElasticLoadBalancingFullAccess.
RESOLVED  Created a dedicated AWSLoadBalancerControllerIAMPolicy granting the specific permissions required and attached it to the load-balancer-controller IAM role. Both the policy and attachment are Terraform resources. Restarted the controller — ingress address populated within seconds.
ISSUE Jenkins could not perform git operations. SSH into the EC2 instance returned -bash: git: command not found. The pipeline failed immediately at the SCM checkout stage.
RESOLVED  Installed git manually with sudo yum install -y git, then permanently added it to the user_data bootstrap script in jenkins.tf so future deployments never require this manual step.
ISSUE ArgoCD install failed with metadata.annotations: Too long: must have at most 262144 bytes. Pods stuck in Pending after install — single t3.small node was at capacity with existing workloads.
RESOLVED  Added --server-side --force-conflicts flags to bypass the annotation size limit. Scaled the node group to 2 nodes — the desired_size = 2 in Terraform is intentional, as ArgoCD requires the second t3.small node to run alongside the application.
04

Jenkins vs GitOps — The Tradeoffs

Jenkins GitHub Actions + ArgoCD
Deployment model Push-based Pull-based
Source of truth Pipeline script Git repository
Self-healing No Yes — ArgoCD corrects drift automatically
Audit trail Build logs Git commit history
Image tagging Jenkins build number Git commit SHA
Infrastructure needed Jenkins EC2 server ArgoCD in-cluster
Best for Enterprise, complex pipelines Cloud-native, GitOps workflows
05

Skills Demonstrated

// Cloud & Infrastructure
  • AWS EKS managed node groups
  • VPC with public/private subnets
  • Application Load Balancer (Terraform)
  • ECR repository management
  • IAM roles via OIDC federation
  • EC2 bootstrapping with user_data
  • NAT Gateway configuration
// Kubernetes & Helm
  • EKS cluster provisioning
  • Helm chart authoring
  • Horizontal Pod Autoscaling
  • Ingress with ALB controller
  • Kubernetes Downward API
  • Metrics Server installation
  • ArgoCD GitOps deployment
// DevOps & Automation
  • Terraform IaC (5 config files)
  • Jenkins declarative pipelines
  • GitHub Actions workflow authoring
  • Docker image build and push
  • Pre-destroy lifecycle hooks
  • Credential management in Jenkins
  • Git branching strategy