diff --git a/projects/03-eks-gitops-argocd/.github/workflows/deploy.yml b/projects/03-eks-gitops-argocd/.github/workflows/deploy.yml
index aee1ea2..019d1d6 100644
--- a/projects/03-eks-gitops-argocd/.github/workflows/deploy.yml
+++ b/projects/03-eks-gitops-argocd/.github/workflows/deploy.yml
@@ -1 +1,31 @@
-#placeholder for now
\ No newline at end of file
+# GitOps CI — validates K8s manifests on push.
+# ArgoCD handles the actual deploy by watching this repo and auto-syncing.
+
+name: Validate K8s Manifests
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "projects/03-eks-gitops-argocd/k8s/**"
+ pull_request:
+ branches: [main]
+ paths:
+ - "projects/03-eks-gitops-argocd/k8s/**"
+ workflow_dispatch:
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Validate manifests (dry-run)
+ run: |
+ kubectl apply --dry-run=client -f projects/03-eks-gitops-argocd/k8s/
+
+ - name: Lint YAML
+ run: |
+ pip install yamllint
+ yamllint projects/03-eks-gitops-argocd/k8s/
diff --git a/projects/03-eks-gitops-argocd/README.md b/projects/03-eks-gitops-argocd/README.md
index e69de29..67eaa91 100644
--- a/projects/03-eks-gitops-argocd/README.md
+++ b/projects/03-eks-gitops-argocd/README.md
@@ -0,0 +1,121 @@
+# Project 03 – EKS GitOps with ArgoCD
+
+Manual kubectl deployments create configuration drift, lack audit trails, and don't scale across teams. This project implements a GitOps delivery pipeline where Git is the single source of truth — every change is version-controlled, peer-reviewed, and automatically reconciled to the cluster by ArgoCD.
+
+## Overview
+
+A production-style GitOps pipeline on Amazon EKS (Fargate) using ArgoCD for continuous delivery. Terraform provisions the infrastructure (VPC, EKS cluster, Fargate profiles, ArgoCD via Helm), and ArgoCD watches this repository for Kubernetes manifest changes and auto-syncs them to the cluster. A sample nginx application demonstrates the full push-to-deploy workflow.
+
+## Architecture
+
+```mermaid
+flowchart LR
+ Dev[Developer] -->|git push| GH[GitHub Repo]
+ GH -->|webhook / poll| Argo[ArgoCD]
+ Argo -->|sync| EKS[EKS Cluster - Fargate]
+
+ subgraph EKS
+ direction TB
+ NS1[argocd namespace]
+ NS2[app namespace]
+ NS2 --> Pod1[nginx pod]
+ NS2 --> Pod2[nginx pod]
+ NS2 --> Svc[ClusterIP Service]
+ end
+```
+
+## Key Features
+
+- **GitOps delivery** — Git push triggers automatic cluster sync via ArgoCD
+- **Self-healing** — ArgoCD reverts manual cluster changes to match the Git state
+- **Automated pruning** — Deleted manifests are automatically removed from the cluster
+- **Fargate-native** — No EC2 nodes to manage; pods run on serverless compute
+- **Infrastructure as Code** — Full VPC, EKS, and ArgoCD provisioned with Terraform
+- **CI validation** — GitHub Actions validates K8s manifests on every push
+
+## Tech Stack
+
+- **Cloud/IaC:** Terraform, AWS VPC, Amazon EKS, Fargate, IAM
+- **GitOps:** ArgoCD (Helm-deployed), Application CRD
+- **App:** nginx on Kubernetes (Deployment, Service, ConfigMap)
+- **CI:** GitHub Actions (manifest validation + YAML lint)
+
+## Prerequisites
+
+- AWS CLI configured with appropriate credentials
+- Terraform >= 1.9.0
+- kubectl
+- ArgoCD CLI (`brew install argocd` or [releases](https://github.com/argoproj/argo-cd/releases))
+- Helm 3
+
+## Quick Start
+
+1. **Provision infrastructure**
+ ```bash
+ cd projects/03-eks-gitops-argocd/infra/terraform
+ terraform init
+ terraform apply
+ ```
+
+2. **Configure kubectl**
+ ```bash
+ aws eks update-kubeconfig --region us-east-1 --name gitops-demo
+ ```
+
+3. **Initialize ArgoCD**
+ ```bash
+ ./scripts/init-argocd.sh
+ ```
+
+4. **Verify the GitOps workflow**
+ - Edit a file in `k8s/` (e.g., change replica count in `deployment.yaml`)
+ - Commit and push
+ - Watch ArgoCD sync: `argocd app get nginx-demo --watch --grpc-web`
+
+## GitOps Workflow
+
+```
+1. Developer pushes manifest change to GitHub
+2. GitHub Actions validates the YAML (dry-run + lint)
+3. ArgoCD detects the new commit (polls every ~3 min)
+4. ArgoCD compares desired state (Git) vs live state (cluster)
+5. ArgoCD applies the diff — pods roll, services update
+6. Self-heal: any manual cluster drift is reverted automatically
+```
+
+## Project Structure
+
+```
+03-eks-gitops-argocd/
+ infra/terraform/ # VPC, EKS, Fargate, ArgoCD Helm release
+ argocd/
+ application.yaml # ArgoCD Application CR
+ helm-chart/values.yaml # ArgoCD Helm overrides
+ k8s/ # App manifests (ArgoCD sync source)
+ namespace.yaml
+ deployment.yaml
+ service.yaml
+ configmap.yaml
+ scripts/
+ init-argocd.sh # Post-deploy setup helper
+ .github/workflows/
+ deploy.yml # CI manifest validation
+```
+
+## Cleanup
+
+```bash
+cd projects/03-eks-gitops-argocd/infra/terraform
+terraform destroy
+```
+
+## Interview Talking Points
+
+- **Why GitOps over CI/CD push?** GitOps provides a declarative, auditable, self-healing delivery model. The cluster always converges to what's in Git — no imperative scripts that can partially fail.
+- **How does ArgoCD detect drift?** It polls the Git repo (configurable interval) and compares the rendered manifests against the live cluster state using a 3-way diff.
+- **Why Fargate?** Eliminates node management overhead. Each pod runs in its own micro-VM, providing workload isolation without managing EC2 instances or AMI updates.
+- **What happens if someone kubectl-edits a resource?** ArgoCD's self-heal reverts it within the next sync cycle, enforcing Git as the single source of truth.
+
+## Why This Project Matters
+
+GitOps is the standard delivery model for Kubernetes-native organizations. This project demonstrates the end-to-end pattern — IaC provisioning, Helm-managed platform components, declarative app delivery, and automated drift correction — the exact workflow used by platform teams at scale.
diff --git a/projects/03-eks-gitops-argocd/argocd/application.yaml b/projects/03-eks-gitops-argocd/argocd/application.yaml
index e69de29..8613b55 100644
--- a/projects/03-eks-gitops-argocd/argocd/application.yaml
+++ b/projects/03-eks-gitops-argocd/argocd/application.yaml
@@ -0,0 +1,24 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: nginx-demo
+ namespace: argocd
+spec:
+ project: default
+
+ source:
+ # Update this URL to your fork/clone of this repo
+ repoURL: https://github.com/TerminalsandCoffee/aws-devops-portfolio.git
+ targetRevision: main
+ path: projects/03-eks-gitops-argocd/k8s
+
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: app
+
+ syncPolicy:
+ automated:
+ prune: true # Remove resources deleted from Git
+ selfHeal: true # Revert manual cluster changes to match Git
+ syncOptions:
+ - CreateNamespace=true
diff --git a/projects/03-eks-gitops-argocd/argocd/helm-chart/values.yaml b/projects/03-eks-gitops-argocd/argocd/helm-chart/values.yaml
index aee1ea2..db11012 100644
--- a/projects/03-eks-gitops-argocd/argocd/helm-chart/values.yaml
+++ b/projects/03-eks-gitops-argocd/argocd/helm-chart/values.yaml
@@ -1 +1,49 @@
-#placeholder for now
\ No newline at end of file
+# ArgoCD Helm values for EKS Fargate deployment
+
+server:
+ service:
+ type: LoadBalancer
+ # Run insecure — TLS terminates at the load balancer
+ extraArgs:
+ - --insecure
+ resources:
+ requests:
+ cpu: 256m
+ memory: 256Mi
+ limits:
+ cpu: 512m
+ memory: 512Mi
+
+controller:
+ resources:
+ requests:
+ cpu: 256m
+ memory: 512Mi
+ limits:
+ cpu: 512m
+ memory: 1Gi
+
+repoServer:
+ resources:
+ requests:
+ cpu: 256m
+ memory: 256Mi
+ limits:
+ cpu: 512m
+ memory: 512Mi
+
+redis:
+ resources:
+ requests:
+ cpu: 128m
+ memory: 128Mi
+ limits:
+ cpu: 256m
+ memory: 256Mi
+
+# Disable components not needed for this demo
+dex:
+ enabled: false
+
+notifications:
+ enabled: false
diff --git a/projects/03-eks-gitops-argocd/docs/architecture.mmd b/projects/03-eks-gitops-argocd/docs/architecture.mmd
new file mode 100644
index 0000000..86f1156
--- /dev/null
+++ b/projects/03-eks-gitops-argocd/docs/architecture.mmd
@@ -0,0 +1,20 @@
+flowchart LR
+ Dev[Developer] -->|git push| GH[GitHub Repo]
+ GH -->|webhook / poll| Argo[ArgoCD]
+ Argo -->|sync manifests| EKS[EKS Cluster]
+
+ subgraph EKS[EKS Cluster - Fargate]
+ direction TB
+ ArgoNS[argocd namespace
ArgoCD Server + Controller]
+ AppNS[app namespace]
+
+ subgraph AppNS[app namespace]
+ Dep[Deployment: nginx x2]
+ Svc[Service: ClusterIP:80]
+ CM[ConfigMap: custom HTML]
+ Dep --> Svc
+ CM -.->|mounted| Dep
+ end
+ end
+
+ GH -.->|GitHub Actions
validates YAML| CI[CI Check]
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/argocd.tf b/projects/03-eks-gitops-argocd/infra/terraform/argocd.tf
index 25b52f5..c01c581 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/argocd.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/argocd.tf
@@ -1,18 +1,12 @@
resource "helm_release" "argocd" {
- name = "argocd"
- repository = "https://argoproj.github.io/argo-helm"
- chart = "argo-cd"
- version = "5.52.1" # latest stable at time of writing
- namespace = "argocd"
+ name = "argocd"
+ repository = "https://argoproj.github.io/argo-helm"
+ chart = "argo-cd"
+ version = "5.52.1"
+ namespace = "argocd"
create_namespace = true
- set {
- name = "server.service.type"
- value = "LoadBalancer"
- }
+ values = [file("${path.module}/../../argocd/helm-chart/values.yaml")]
- set {
- name = "server.extraArgs"
- value = "{--insecure}"
- }
-}
\ No newline at end of file
+ depends_on = [module.eks]
+}
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/backend.tf b/projects/03-eks-gitops-argocd/infra/terraform/backend.tf
deleted file mode 100644
index 12c0dbe..0000000
--- a/projects/03-eks-gitops-argocd/infra/terraform/backend.tf
+++ /dev/null
@@ -1,3 +0,0 @@
-terraform {
- backend "s3" {}
-}
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/eks.tf b/projects/03-eks-gitops-argocd/infra/terraform/eks.tf
index 80c9527..dc94d19 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/eks.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/eks.tf
@@ -7,7 +7,7 @@ module "eks" {
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_endpoint_private_access = true
- cluster_endpoint_public_access = false
+ cluster_endpoint_public_access = true
# Enable Container Insights
cluster_enabled_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/helm_providers.tf b/projects/03-eks-gitops-argocd/infra/terraform/helm_providers.tf
index 733d4c5..6878aa8 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/helm_providers.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/helm_providers.tf
@@ -1,5 +1,5 @@
data "aws_eks_cluster_auth" "this" {
- name = module.eks.cluster_id
+ name = module.eks.cluster_name
}
provider "kubernetes" {
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/main.tf b/projects/03-eks-gitops-argocd/infra/terraform/main.tf
index d63dbd1..79cb76c 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/main.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/main.tf
@@ -1,15 +1,9 @@
-output "cluster_endpoint" {
- value = module.eks.cluster_endpoint
-}
-
-output "cluster_name" {
- value = module.eks.cluster_id
-}
-
-output "configure_kubectl" {
- value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_id}"
-}
-
-output "argocd_url" {
- value = "http://${helm_release.argocd.status[0].load_balancer_ingress[0].hostname}"
-}
\ No newline at end of file
+# Root module — resources are organized in dedicated files:
+# vpc.tf – VPC and networking
+# eks.tf – EKS cluster and Fargate profiles
+# argocd.tf – ArgoCD Helm release
+# helm_providers.tf – Kubernetes/Helm provider config
+# outputs.tf – All outputs
+# variables.tf – All variables
+# versions.tf – Terraform/provider versions + backend
+# provider.tf – AWS provider
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/outputs.tf b/projects/03-eks-gitops-argocd/infra/terraform/outputs.tf
index ffd8adc..23ee129 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/outputs.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/outputs.tf
@@ -1,11 +1,19 @@
output "cluster_name" {
- value = module.eks.cluster_name
+ description = "EKS cluster name"
+ value = module.eks.cluster_name
}
output "cluster_endpoint" {
- value = module.eks.cluster_endpoint
+ description = "EKS cluster API endpoint"
+ value = module.eks.cluster_endpoint
+}
+
+output "configure_kubectl" {
+ description = "Command to configure kubectl"
+ value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_name}"
}
output "argocd_url" {
- value = kubernetes_service.argocd_server.metadata[0].name
+ description = "ArgoCD server URL (available after Helm release completes)"
+ value = "Run: kubectl get svc argocd-server -n argocd -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'"
}
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/provider.tf b/projects/03-eks-gitops-argocd/infra/terraform/provider.tf
index 349328c..c9d7ccb 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/provider.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/provider.tf
@@ -1,31 +1,3 @@
-terraform {
- required_version = ">= 1.9.0"
-
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = "~> 5.0"
- }
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.0"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.0"
- }
- }
-
- # Remote S3 backend – create the bucket once (see script at end)
- backend "s3" {
- bucket = "yourname-devops-portfolio-tfstate"
- key = "eks-gitops/terraform.tfstate"
- region = "us-east-1"
- dynamodb_table = "terraform-locks"
- encrypt = true
- }
-}
-
provider "aws" {
region = var.aws_region
-}
\ No newline at end of file
+}
diff --git a/projects/03-eks-gitops-argocd/infra/terraform/versions.tf b/projects/03-eks-gitops-argocd/infra/terraform/versions.tf
index 85d46c0..2d80a07 100644
--- a/projects/03-eks-gitops-argocd/infra/terraform/versions.tf
+++ b/projects/03-eks-gitops-argocd/infra/terraform/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.0"
+ required_version = ">= 1.9.0"
required_providers {
aws = {
@@ -15,4 +15,12 @@ terraform {
version = "~> 2.12"
}
}
+
+ backend "s3" {
+ bucket = "yourname-devops-portfolio-tfstate"
+ key = "eks-gitops/terraform.tfstate"
+ region = "us-east-1"
+ dynamodb_table = "terraform-locks"
+ encrypt = true
+ }
}
diff --git a/projects/03-eks-gitops-argocd/k8s/configmap.yaml b/projects/03-eks-gitops-argocd/k8s/configmap.yaml
new file mode 100644
index 0000000..94072a2
--- /dev/null
+++ b/projects/03-eks-gitops-argocd/k8s/configmap.yaml
@@ -0,0 +1,55 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-html
+ namespace: app
+data:
+ index.html: |
+
+
+
Deployed via ArgoCD on Amazon EKS (Fargate)
+Push to Git → ArgoCD syncs → Cluster updates automatically
+ EKS + ArgoCD + GitOps +