Screenshot from the article

Introduction

In modern DevOps workflows, Infrastructure as Code (IaC) and CI/CD pipelines play a crucial role in ensuring scalable, repeatable, and automated deployments. AWS EKS (Elastic Kubernetes Service) allows teams to run Kubernetes clusters without managing the control plane, while Terraform helps with automated infrastructure provisioning.

In this guide, we’ll build an end-to-end DevOps pipeline to:

✅ Provision an EKS cluster with Terraform✅ Containerize a sample application using Docker✅ Push the image to Amazon ECR (Elastic Container Registry)✅ Deploy the app to Kubernetes using Helm✅ Automate everything with a GitHub Actions CI/CD pipeline

🚀 Architecture Overview

The workflow consists of the following steps:

1️⃣ Terraform provisions AWS infrastructure (VPC, EKS, IAM roles, security groups)2️⃣ Docker builds & pushes a containerized app to AWS ECR3️⃣ Kubernetes manifests (or Helm) deploy the app to the EKS cluster4️⃣ GitHub Actions automates CI/CD pipeline execution

1️⃣ Provisioning AWS EKS with Terraform

Step 1: Install Prerequisites

Ensure you have the following installed:

  • Terraform (>=1.5.0)
  • AWS CLI (aws configure)
  • Kubectl (eksctl install)
  • Helm (helm install)

Step 2: Create Terraform Configuration for EKS

main.tf - Define the EKS cluster:

provider "aws" {
  region = "us-east-1"
}
resource "aws_iam_role" "eks_cluster_role" {
  name = "eks-cluster-role"
  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "eks.amazonaws.com" }
    }]
  })
}
resource "aws_eks_cluster" "my_eks" {
  name     = "my-cluster"
  role_arn = aws_iam_role.eks_cluster_role.arn
  
  vpc_config {
    subnet_ids = [aws_subnet.public_1.id, aws_subnet.public_2.id]
  }
}
resource "aws_eks_node_group" "node_group" {
  cluster_name    = aws_eks_cluster.my_eks.name
  node_role_arn   = aws_iam_role.eks_cluster_role.arn
  subnet_ids      = [aws_subnet.public_1.id, aws_subnet.public_2.id]
  instance_types  = ["t3.medium"]
  desired_size    = 2
}

Step 3: Apply Terraform Configuration

terraform init
terraform apply -auto-approve

Verify the cluster:

aws eks --region us-east-1 update-kubeconfig --name my-cluster
kubectl get nodes

2️⃣ Build and Push Docker Image to Amazon ECR

Step 1: Create an ECR Repository

aws ecr create-repository --repository-name my-app

Step 2: Build and Push Docker Image

Dockerfile - Define containerized application:

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
EXPOSE 3000

Authenticate and push to ECR:

aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account_id>.dkr.ecr.us-east-1.amazonaws.com
docker build -t my-app .
docker tag my-app:latest <account_id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
docker push <account_id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest

3️⃣ Deploying to Kubernetes (EKS) using Helm

Step 1: Create Kubernetes Deployment Manifest

deployment.yaml - Define Kubernetes deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: <account_id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
          ports:
            - containerPort: 3000

Apply the deployment:

kubectl apply -f deployment.yaml
kubectl get pods

Step 2: Expose the Application

service.yaml - Define Kubernetes service:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

Apply the service:

kubectl apply -f service.yaml
kubectl get svc

4️⃣ Automating Everything with GitHub Actions

.github/workflows/deploy.yaml - Define CI/CD pipeline:

name: CI/CD Pipeline
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v2
      - name: Login to AWS ECR
        run: aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account_id>.dkr.ecr.us-east-1.amazonaws.com
      - name: Build and Push Docker Image
        run: |
          docker build -t my-app .
          docker tag my-app:latest <account_id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
          docker push <account_id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
      - name: Deploy to Kubernetes
        run: |
          kubectl apply -f deployment.yaml
          kubectl apply -f service.yaml

🚀 Best Practices

✅ Use Terraform remote state with S3 + DynamoDB✅ Implement secrets management with AWS Secrets Manager✅ Use Helm charts for better Kubernetes deployments✅ Integrate AWS CloudWatch for monitoring

❓ FAQs

1️⃣ How do I update my app after changes?

  • Push changes to main branch, and GitHub Actions will rebuild and deploy automatically.

2️⃣ How do I rollback a deployment?

  • kubectl rollout undo deployment my-app

3️⃣ Can I integrate AWS ALB with EKS?

  • Yes! Use the AWS ALB Ingress Controller to expose services via ALB.

4️⃣ How do I monitor the application?

  • Use AWS CloudWatch and kubectl logs -f <pod-name> for debugging.

Share your thoughts below! 👇

📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!

Originally published on Medium.