Simple project with:
- Create account (no verification)
- Login
- Show
Welcome, usernameafter login - Colorful frontend theme
- Frontend: React (Vite)
- Backend: Node.js + Express
- Database: PostgreSQL (local Docker or AWS RDS)
- Infrastructure: Terraform (AWS EKS + RDS + VPC)
- Ingress: Traefik on EKS (Classic ELB via AWS Cloud Controller)
- CI/CD: GitHub Actions + ArgoCD (GitOps)
Lumina/
├── app/
│ ├── backend/ # Node.js + Express API
│ └── frontend/ # React (Vite)
├── K8s/
│ ├── base/
│ │ ├── frontend/
│ │ │ ├── deployment.yaml
│ │ │ └── service.yaml
│ │ └── backend/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── secret.yaml # RDS credentials (gitignored)
│ ├── ingress-controller/
│ │ ├── install.sh
│ │ └── values.yaml
│ └── argocd/
│ ├── install.sh
│ ├── backend-app.yaml
│ └── frontend-app.yaml
├── .github/
│ └── workflows/
│ ├── backend.yml # Build, push, update image tag
│ └── frontend.yml # Build, push, update image tag
└── terraform/
├── DEV/ # Root module (entry point)
└── modules/
├── networking/ # VPC, subnets, IGW, NAT
├── eks_cluster/ # EKS control plane + IAM
├── eks_cluster_nodes/ # Self-managed nodes + ASG
└── rds/ # PostgreSQL RDS instance
POST /api/auth/register- body:
{ "username": "demo", "email": "demo@mail.com", "password": "secret123" }
- body:
POST /api/auth/login- body:
{ "identifier": "demo", "password": "secret123" }
- body:
docker compose up -dStarts PostgreSQL on localhost:5432:
- Database:
auth_app - User:
postgres - Password:
postgres
Table creation runs automatically from app/backend/database/init.sql.
cd app/backend
npm install
npm run devBackend runs on http://localhost:4000. Environment file: app/backend/.env.
cd app/frontend
npm install
npm run devFrontend runs on http://localhost:5173.
Build images:
docker build -t lumina-backend:latest ./app/backend
docker build -t lumina-frontend:latest --build-arg VITE_API_URL=http://localhost:4000 ./app/frontendRun containers:
docker run -d --name lumina-postgres -p 5432:5432 \
-e POSTGRES_DB=appdb \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
postgres:16
docker run -d --name lumina-backend -p 4000:4000 --env-file app/backend/.env.docker lumina-backend:latest
docker run -d --name lumina-frontend -p 8080:80 lumina-frontend:latest- Frontend available at
http://localhost:8080 - Vite variables are baked at build time — pass
VITE_API_URLvia--build-arg - Remove existing containers first if needed:
docker rm -f lumina-frontend lumina-backend lumina-postgres
Push code to main
↓
GitHub Actions
├── Build Docker image
├── Push to Docker Hub (tagged with git SHA)
└── Commit new image tag to k8s/base/*/deployment.yaml
↓
ArgoCD detects Git change
↓
ArgoCD syncs deployment to EKS automatically
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub username |
DOCKERHUB_TOKEN |
Docker Hub access token |
VITE_API_URL |
Backend URL baked into frontend image (e.g. http://<elb-hostname>) |
GH_PAT |
GitHub Personal Access Token with repo scope — allows Actions to push image tag commits back to the repo |
Generate
GH_PATat: GitHub → Settings → Developer Settings → Personal Access Tokens
| Workflow | Trigger | What it does |
|---|---|---|
backend.yml |
Push to app/backend/** |
Builds & pushes backend image, updates k8s/base/backend/deployment.yaml |
frontend.yml |
Push to app/frontend/** |
Builds & pushes frontend image, updates k8s/base/frontend/deployment.yaml |
- AWS credentials configured
- Terraform >= 1.0
cd terraform/DEV
terraform init
terraform plan -var="db_password=yourpassword"
terraform apply -var="db_password=yourpassword"This provisions:
- VPC with public/private subnets (tagged for EKS ELB discovery)
- EKS cluster (
dev-cluster) with self-managed node group (2xt3.medium) - RDS PostgreSQL (
db.t3.micro, private subnets, no public access)
terraform output rds_endpoint| Module | Description |
|---|---|
networking |
VPC, subnets, IGW, NAT Gateway, route tables |
eks_cluster |
EKS control plane, IAM role |
eks_cluster_nodes |
Launch template, ASG, node IAM role |
rds |
RDS PostgreSQL instance, subnet group, security group |
KodeKloud note:
performance_insightsandiam_database_authenticationare disabled due to sandbox IAM restrictions.authentication_modeis set toAPI_AND_CONFIG_MAPto match the pre-created cluster state.
After terraform apply, update your local kubeconfig to connect to the cluster:
aws eks update-kubeconfig --region us-east-1 --name dev-clusterVerify connection:
kubectl get nodesNodes will show NotReady at this point — that's expected until aws-auth is applied.
This allows the worker nodes to join the cluster. Get the node IAM role ARN from Terraform output first:
cd terraform/DEV
terraform output eks_node_role_arnThen update rolearn in terraform/aws-auth-cm.yaml with the actual ARN:
data:
mapRoles: |
- rolearn: arn:aws:iam::<account-id>:role/<node-role-name>
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodesApply it:
kubectl apply -f terraform/aws-auth-cm.yamlVerify nodes are ready:
kubectl get nodesAll nodes should show Ready status within a few minutes.
cd K8s/ingress-controller
bash install.shVerify:
kubectl -n traefik get pods
kubectl -n traefik get svc # EXTERNAL-IP should show ELB hostname
kubectl get ingressclasscd K8s/argocd
bash install.shGet admin password and URL:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
kubectl -n argocd get svc argocd-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'1) Update repoURL in both app manifests:
# K8s/argocd/backend-app.yaml and frontend-app.yaml
source:
repoURL: https://github.com/<your-username>/<your-repo>.git2) Apply:
kubectl apply -f K8s/argocd/backend-app.yaml
kubectl apply -f K8s/argocd/frontend-app.yamlArgoCD will now automatically sync any changes to K8s/base/ to the EKS cluster.
1) Fill in RDS credentials in K8s/base/backend/secret.yaml:
stringData:
DB_HOST: "<terraform output rds_endpoint>"
DB_PORT: "5432"
DB_NAME: "appdb"
DB_USER: "postgres"
DB_PASSWORD: "<your-db-password>"
JWT_SECRET: "<your-jwt-secret>"2) Apply the secret manually (it is gitignored):
kubectl apply -f K8s/base/backend/secret.yamlRDS (Terraform) → secret.yaml → K8s Secret → backend Pod (env vars via envFrom)
secret.yamlis gitignored — never commit real credentials.
- No email verification required for account creation
- Passwords are hashed with bcrypt
- Login accepts username or email
- For in-cluster frontend-to-backend communication, use
http://lumina-backend-service:4000