Deploying a 3-Tier App on AWS EKS Made Simple

Table of contents
- π§± What Is a 3-Tier App?
- βοΈ AWS Services We'll Use
- ποΈ Setting Up PostgreSQL on RDS
- π Kubernetes Secrets & Configs
- π Run DB Migrations Using Kubernetes Job
- π₯οΈ Deploy Backend and Frontend
- π Expose via AWS Load Balancer Controller
- π Create Ingress
- π§ Custom Domain Setup
- β Wrapping Up
- β FAQs
Overview
π§± What Is a 3-Tier App?
A 3-tier architecture splits your application into:
Presentation Layer (Frontend) β The UI users interact with.
Logic Layer (Backend) β Handles API requests, business logic.
Data Layer (Database) β Stores persistent data.
This architecture is scalable, secure, and modularβperfect for cloud-native apps.
βοΈ AWS Services We'll Use
EKS for container orchestration
RDS (PostgreSQL) for managed databases
ALB Controller for exposing services
Route 53 for custom domains
π Why Use AWS EKS?
Benefits of Kubernetes on AWS
Scalable orchestration
Declarative configuration
CI/CD ready
EKS Options Explained
Managed Node Groups: AWS manages worker nodes for you.
Fargate: Serverless, but no native support for persistent volumes.
Self-Managed Nodes: Full control, higher complexity.
Why Use Managed Node Groups?
π§ Prerequisites
Install Required Tools
brew install awscli kubectl eksctl helm
aws cli login
aws configure
ποΈ Creating an EKS Cluster :
eksctl create cluster \ --name my-cluster \ --region eu-west-1 \ --version 1.31 \ --nodegroup-name managed-nodes \ --node-type t3.medium \ --nodes 2 \ --nodes-min 1 \ --nodes-max 3 \ --managed
Update kubeconfig
aws eks update-kubeconfig --name my-cluster --region eu-west-1 kubectl get nodes
ποΈ Setting Up PostgreSQL on RDS
ποΈ Setting Up PostgreSQL on RDS
Get VPC ID and Subnets
VPC_ID=$(aws eks describe-cluster --name my-cluster \ --region eu-west-1 \ --query "cluster.resourcesVpcConfig.vpcId" --output text)
Get private subnets (assuming 3 AZs):
SUBNET_IDS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \ --query "Subnets[?MapPublicIpOnLaunch==\`false\`].SubnetId" --output text)
Create DB Subnet Group
aws rds create-db-subnet-group \ --db-subnet-group-name postgres-subnet-group \ --description "Private subnets for RDS" \ --subnet-ids $SUBNET_IDS \ --region eu-west-1
Create RDS Security Group
RDS_SG_ID=$(aws ec2 create-security-group \ --group-name rds-sg \ --description "RDS SG" \ --vpc-id $VPC_ID \ --query GroupId --output text)
Authorize EKS Access to PostgreSQL
Get EKS worker SG:
EKS_SG_ID=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" \ "Name=group-name,Values=*nodes*" --query "SecurityGroups[0].GroupId" --output text)
Allow access:
aws ec2 authorize-security-group-ingress \ --group-id $RDS_SG_ID \ --protocol tcp \ --port 5432 \ --source-group $EKS_SG_ID
Create PostgreSQL Instance
aws rds create-db-instance \ --db-instance-identifier my-postgres \ --db-instance-class db.t3.small \ --engine postgres \ --engine-version 15 \ --allocated-storage 20 \ --master-username postgresadmin \ --master-user-password 'YourStrongPassword123!' \ --db-subnet-group-name postgres-subnet-group \ --vpc-security-group-ids $RDS_SG_ID \ --no-publicly-accessible \ --region eu-west-1
π Kubernetes Secrets & Configs
Create Namespace
# namespace.yaml apiVersion: v1 kind: Namespace metadata: name: 3-tier-app-eks
kubectl apply -f namespace.yaml
Create Secrets
# secrets.yaml apiVersion: v1 kind: Secret metadata: name: db-secret namespace: 3-tier-app-eks type: Opaque data: username: cG9zdGdyZXNhZG1pbg== # postgresadmin (base64) password: WW91clN0cm9uZ1Bhc3N3b3JkMTIzIQ== # YourStrongPassword123!
Create ConfigMap
# config.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: 3-tier-app-eks data: DATABASE_HOST: your-rds-endpoint.rds.amazonaws.com
π Run DB Migrations Using Kubernetes Job
# migration-job.yaml apiVersion: batch/v1 kind: Job metadata: name: db-migrate namespace: 3-tier-app-eks spec: template: spec: containers: - name: migrate image: my-backend-image command: ["flask", "db", "upgrade"] envFrom: - secretRef: name: db-secret - configMapRef: name: app-config restartPolicy: OnFailure
kubectl apply -f migration-job.yaml kubectl logs job/db-migrate -n 3-tier-app-eks
π₯οΈ Deploy Backend and Frontend
Flask Backend
# backend.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend namespace: 3-tier-app-eks spec: replicas: 2 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: my-backend-image ports: - containerPort: 8000 envFrom: - secretRef: name: db-secret - configMapRef: name: app-config --- apiVersion: v1 kind: Service metadata: name: backend namespace: 3-tier-app-eks spec: selector: app: backend ports: - port: 8000 targetPort: 8000
React Frontend
# frontend.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: 3-tier-app-eks spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: my-frontend-image ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: frontend namespace: 3-tier-app-eks spec: selector: app: frontend ports: - port: 80 targetPort: 80
π Expose via AWS Load Balancer Controller
Associate OIDC
eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve
Create IAM Policy
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json aws iam create-policy \ --policy-name AWSLoadBalancerControllerIAMPolicy \ --policy-document file://iam_policy.json
IAM Service Account
eksctl create iamserviceaccount \ --cluster=my-cluster \ --namespace=kube-system \ --name=aws-load-balancer-controller \ --attach-policy-arn=arn:aws:iam::[ACCOUNT_ID]:policy/AWSLoadBalancerControllerIAMPolicy \ --approve
Install ALB Controller
helm repo add eks https://aws.github.io/eks-charts helm repo update helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=my-cluster \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller \ --set region=eu-west-1 \ --set vpcId=$VPC_ID
π Create Ingress
# ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: 3-tier-app-ingress namespace: 3-tier-app-eks annotations: alb.ingress.kubernetes.io/scheme: internet-facing spec: ingressClassName: alb rules: - http: paths: - path: /api pathType: Prefix backend: service: name: backend port: number: 8000 - path: / pathType: Prefix backend: service: name: frontend port: number: 80
kubectl apply -f ingress.yaml
Tag Public Subnets
aws ec2 create-tags --resources subnet-xxxx subnet-yyyy \ --tags Key=kubernetes.io/role/elb,Value=1
π§ Custom Domain Setup
Go to Route 53
Create a new Hosted Zone
Add Alias Record for the ALB DNS
Update domain registrar nameservers
β Wrapping Up
Youβve successfully deployed a modern, cloud-native 3-tier application using Kubernetes on AWS EKS. Along the way, you:
Created a secure PostgreSQL database
Managed secrets and configs
Handled database migrations
Exposed your app publicly via ALB and custom domain
β FAQs
1. How do I scale the application?
Use kubectl scale deployment backend --replicas=5
to scale pods.
2. Can I use EFS for shared storage?
Yes, EFS is supported via Persistent Volumes on EKS.
3. Is this setup production-ready?
Yes, with minor enhancements like SSL termination, monitoring, and autoscaling.
4. Can I CI/CD this setup?
Definitely. Use GitHub Actions or AWS CodePipeline with Helm and kubectl.
5. How do I handle updates?
Use rolling updates in Deployment specs or Helm upgrades.
Subscribe to my newsletter
Read articles from Pratiksha kadam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
