eks-microservices/
infra/
network/
main.tf
variables.tf
outputs.tf
eks/
main.tf
variables.tf
outputs.tf
env/
backend.tf # Terraform remote state (optional)
provider.tf # AWS + Kubernetes providers
variables.tf
terraform.tfvars # Your environment values (region, names)
README.md
- Install Terraform, AWS CLI, kubectl, and Docker if not already installed.
- Configure AWS CLI with your credentials:
aws configure //(Enter your Access Key, Secret Key, region, and output format.)- In your workspace, create the structure:
mkdir -p eks-microservices/infra/network
mkdir -p eks-microservices/infra/eks
mkdir -p eks-microservices/env
cd eks-microservices- Add empty files:
touch infra/network/{main.tf,variables.tf,outputs.tf}
touch infra/eks/{main.tf,variables.tf,outputs.tf}
touch env/{backend.tf,provider.tf,variables.tf,terraform.tfvars,main.tf}
touch README.mdmodule "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.cluster_name}-vpc"
cidr = "10.0.0.0/16"
azs = var.azs
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
# Tags for Kubernetes load balancers
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
}
tags = {
Project = "eks-microservices"
}
}variable "cluster_name" {
type = string
}
variable "azs" {
type = list(string)
}output "vpc_id" {
value = module.vpc.vpc_id
}
output "private_subnets" {
value = module.vpc.private_subnets
}
output "public_subnets" {
value = module.vpc.public_subnets
}
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = var.cluster_name
cluster_version = "1.29"
# Networking from your VPC module
vpc_id = var.vpc_id
subnet_ids = var.private_subnets
# Enable IAM Roles for Service Accounts (IRSA)
enable_irsa = true
# Managed node groups
eks_managed_node_groups = {
ng-default = {
instance_types = ["t3.micro"]
desired_size = 2
min_size = 2
max_size = 4
capacity_type = "ON_DEMAND"
}
}
tags = {
Project = "eks-microservices"
}
}
data "aws_eks_cluster_auth" "this" {
name = module.eks.cluster_name
}
output "cluster_name" {
value = module.eks.cluster_name
}
output "cluster_endpoint" {
value = module.eks.cluster_endpoint
}
output "cluster_certificate_authority_data" {
value = module.eks.cluster_certificate_authority_data
}variable "cluster_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "private_subnets" {
type = list(string)
} output "eks_managed_node_groups" {
description = "Details of the managed node groups"
value = module.eks.eks_managed_node_groups
}locals {
project = "eks-microservices"
}
# Call the network module
module "network" {
source = "../infra/network"
cluster_name = var.cluster_name
azs = var.azs
}
# Call the EKS module, passing outputs from network
module "eks" {
source = "../infra/eks"
cluster_name = var.cluster_name
vpc_id = module.network.vpc_id
private_subnets = module.network.private_subnets
}terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
}
}
provider "aws" {
region = var.region
}
# Kubernetes provider (optional, used after cluster is created)
provider "kubernetes" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
token = data.aws_eks_cluster_auth.this.token
}# AWS region where you want to deploy
region = "us-east-1"
# Name of your EKS cluster
cluster_name = "eks-microservices"
# Availability Zones (AZs) for subnets
azs = ["us-east-1a", "us-east-1b"]variable "region" {
description = "AWS region to deploy resources"
type = string
}
variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
}
variable "azs" {
description = "Availability Zones for the VPC subnets"
type = list(string)
}output "cluster_name" {
value = module.eks.cluster_name
}
output "cluster_endpoint" {
value = module.eks.cluster_endpoint
}
output "cluster_certificate_authority_data" {
value = module.eks.cluster_certificate_authority_data
}
output "vpc_id" {
value = module.network.vpc_id
}
output "private_subnets" {
value = module.network.private_subnets
}terraform init
terraform plan
terraform apply -auto-approveaws eks update-kubeconfig --name eks-microservices --region us-east-1
kubectl get nodesaws eks create-access-entry \
--cluster-name eks-microservices \
--principal-arn arn:aws:iam::96974892****:user/Maria \
--type STANDARD \
--region us-east-1
Associate Kubernetes Group // This gives your IAM user full admin access (system:masters) via the managed policy.
aws eks associate-access-policy \
--cluster-name eks-microservices \
--principal-arn arn:aws:iam::96974892***:user/Maria \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
--access-scope type=cluster \
--region us-east-1
aws eks update-kubeconfig --region us-east-1 --name eks-microservices
kubectl get nodes
frontend/ ├── Dockerfile ├── package.json ├── package-lock.json # auto-generated after npm install ├── index.html # Vite entry point ├── src/ # React source code │ ├── main.jsx
# Frontend Service (React + Vite + Nginx)
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
EXPOSE 80- Run
npm init -y // This will create a default `package.json` file inside `frontend/`.{
"name": "frontend",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^7.2.7"
}
}import React from 'react'
import ReactDOM from 'react-dom/client'
function App() {
return <h1>Hello, Maria’s EKS Microservices Frontend 🚀</h1>
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>- Depending on your frontend stack:React (CRA)
npm install // run inside frontend foldernpm audit fix --force- Verify Locally
npm run build // run inside frontend folder → This should generate a `dist/` folder.docker build -t frontend:latest ./frontend
user-service/ ├── Dockerfile ├── requirements.txt └── user_service.py
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "user_service.py"]flask==2.3.2
docker build -t user-service:latest ./user-service
order-service/ ├── Dockerfile ├── package.json ├── package-lock.json └── order_service.js
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "order_service.js"]- Run // inside the order-service folder
npm init -y
npm install express
docker build -t frontend:latest ./frontend
docker build -t user-service:latest ./user-service
docker build -t order-service:latest ./order-serviceAll three microservices images (frontend, user-service, order-service) were successfully built
docker images
docker run -p 3000:80 frontend:latest
docker run -p 5000:5000 user-service:latest
docker run -p 4000:4000 order-service:latest
- Using AWS CLI:
aws ecr create-repository --repository-name frontend --region us-east-1aws ecr create-repository --repository-name user-service --region us-east-1aws ecr create-repository --repository-name order-service --region us-east-1- This will return JSON output with the repository URI, e.g.:
{
"repository": {
"repositoryArn": "arn:aws:ecr:us-east-1:12345678***:repository/frontend",
"repositoryUri": "12345678***.dkr.ecr.us-east-1.amazonaws.com/frontend"
}
}
- 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- Tag the local image with the ECR URI:
docker tag frontend:latest 96974*****.dkr.ecr.us-east-1.amazonaws.com/frontend
docker tag user-service:latest 1234567****.dkr.ecr.us-east-1.amazonaws.com/user-service:latest
docker tag order-service:latest 12345678****.dkr.ecr.us-east-1.amazonaws.com/order-service:latest- Push it:
docker push 12345678****.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
docker push 12345678****.dkr.ecr.us-east-1.amazonaws.com/user-service:latest
docker push 12345678****.dkr.ecr.us-east-1.amazonaws.com/order-service:latesteks-microservices/ └── k8s/ ├── frontend-deployment.yaml ├── user-service-deployment.yaml ├── order-service-deployment.yaml └── services.yaml
- In each
Deployment.yaml, reference the ECR image URI:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: 346748929****.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-deployment
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: 879748929***.dkr.ecr.us-east-1.amazonaws.com/user-service:latest
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "your-db-url-here"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service-deployment
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: 56697489****.dkr.ecr.us-east-1.amazonaws.com/order-service:latest
ports:
- containerPort: 4000
env:
- name: DATABASE_URL
value: "your-db-url-here"
livenessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 4000
initialDelaySeconds: 5
periodSeconds: 10
# Frontend Service (public)
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
# User Service (internal)
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ClusterIP
selector:
app: user-service
ports:
- port: 5000
targetPort: 5000
---
# Order Service (internal)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
type: ClusterIP
selector:
app: order-service
ports:
- port: 4000
targetPort: 4000
- Ensure your EKS node IAM role has permissions to pull from ECR (
AmazonEC2ContainerRegistryReadOnlypolicy).
- Run
kubectl apply -f frontend-deployment.yaml
kubectl apply -f user-service-deployment.yaml
kubectl apply -f order-service-deployment.yaml
kubectl apply -f services.yaml- Wait for the frontend LoadBalancer to get an
EXTERNAL-IP:
kubectl get pods
kubectl get deployments
kubectl get svc frontend-service- Pods should pull images from ECR and enter Running ✅ state.
- Once it’s ready, open that IP in your browser — it should load your React frontend and connect to the backend services internally.
- In your repo root, create a folder:
.github/workflows/ - This is where GitHub looks for workflow files.
- Inside
.github/workflows/, create a file named: (deploy.yml)
name: CI/CD to EKS
on:
push:
branches: [ "main" ]
jobs:
frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin *****456789.dkr.ecr.us-east-1.amazonaws.com
- name: Build and push Docker image
run: |
IMAGE_URI=*****456789.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
docker build -t $IMAGE_URI -f frontend/Dockerfile ./frontend
docker push $IMAGE_URI
- name: Deploy to EKS
run: |
aws eks --region us-east-1 update-kubeconfig --name eks-microservices
kubectl apply -f k8s/frontend-deployment.yaml
user-service:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin *****456789.dkr.ecr.us-east-1.amazonaws.com
- name: Build and push Docker image
run: |
IMAGE_URI=*****456789.dkr.ecr.us-east-1.amazonaws.com/user-service:latest
docker build -t $IMAGE_URI -f user-service/Dockerfile ./user-service
docker push $IMAGE_URI
- name: Deploy to EKS
run: |
aws eks --region us-east-1 update-kubeconfig --name eks-microservices
kubectl apply -f k8s/user-service-deployment.yaml
order-service:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin *****456789.dkr.ecr.us-east-1.amazonaws.com
- name: Build and push Docker image
run: |
IMAGE_URI=*****456789.dkr.ecr.us-east-1.amazonaws.com/order-service:latest
docker build -t $IMAGE_URI -f order-service/Dockerfile ./order-service
docker push $IMAGE_URI
- name: Deploy to EKS
run: |
aws eks --region us-east-1 update-kubeconfig --name eks-microservices
kubectl apply -f k8s/order-service-deployment.yaml - At the top of
deploy.yml, specify when the workflow should run:name: CI/CD to EKS on: push: branches: [ "main" ] - This ensures the pipeline runs automatically on every push to
main.
- Define jobs for each microservice (frontend, user-service, order-service):
jobs:
frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- Repeat for
user-serviceandorder-service.
- In your GitHub repo, go to:
Settings → Secrets and variables → Actions → New repository secret →Add
AWS_ACCESS_KEY_ID→ paste the key IDAWS_SECRET_ACCESS_KEY→ paste the secret key- (Optional)
AWS_REGION→ e.g.,us-east-1
- Add:
- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin <your-account-id>.dkr.ecr.us-east-1.amazonaws.co
- Example for frontend:
- name: Build and push Docker image
run: |
IMAGE_URI=<your-account-id>.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
docker build -t $IMAGE_URI -f frontend/Dockerfile ./frontend
docker push $IMAGE_URI
- Apply manifests automatically:
- name: Deploy to EKS
run: |
aws eks --region us-east-1 update-kubeconfig --name eks-microservices
kubectl apply -f k8s/frontend-deployment.yaml
Every push to main now triggers build → push → deploy → validate across all microservices
- Delivered a scalable, resilient Kubernetes foundation to host microservices and accelerate cloud‑native delivery.
- All microservices containerized and stored in Amazon ECR
- Microservices successfully deployed on Amazon EKS
- Secure integration between ECR and EKS established
- Frontend accessible externally, backend services communicating internally
- Solid foundation for scaling, monitoring, and CI/CD integration
- Achieved a fully automated CI/CD pipeline for multi‑service architecture on Amazon EKS
- Every push to main now triggers build → push → deploy → validate across all microservices
- Reduced manual effort, improved reliability, and ensured production‑ready deployments with rollback safety