Skip to content

Latest commit

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node : Production Backend on Kubernetes + AWS EKS

An educational, end-to-end demo of taking a real backend to production on Kubernetes. Observability, infrastructure-as-code, CI/CD, TLS and autoscaling: the parts around the app that actually matter. Fork it, deploy it, break it, tear it down.

BunExpressPrismaPostgreSQLDocker
KubernetesAWS EKSTerraformPrometheusGrafana

What this teaches

By the end you will have deployed the same backend three ways (laptop → local Kubernetes → AWS) and seen what changes at each step:

StageWhereYou learn
Local devBun + Docker ComposeHow the app runs before any cluster is involved
Local clusterMinikubeKubernetes manifests, services, HPA, monitoring, free on your laptop
ProductionAWS EKSThe real thing: Terraform, managed nodes, load balancers, DNS, SSL, secrets

The Kubernetes manifests are the same on Minikube and EKS. That's the whole point: Kubernetes is the portability layer, and only the surrounding AWS glue differs.


The stack (and why each piece is here)

ConcernToolWhy it matters in production
Runtime / APIBun + Express 5 + Prisma + PostgreSQLThe app itself, kept boring on purpose
ContainerizationDocker (multi-stage)Same image runs everywhere; small, reproducible builds
OrchestrationKubernetesSelf-healing, rolling updates, autoscaling
MetricsPrometheusScrapes /metrics: request rate, latency (p50/p95/p99), error rate
DashboardsGrafanaVisualizes Prometheus + Loki; ships with pre-built dashboards
LogsLoki + WinstonCentralized, queryable logs from every pod
Infrastructure as CodeTerraformThe entire AWS footprint is versioned and reproducible; no clicking in the console
CloudAWS EKSManaged Kubernetes control plane
CI/CDGitHub Actionsgit tag → test → build → push → rolling deploy
TLS / DNSACM + Route 53Free auto-renewing SSL, custom domains

What a "production-ready" backend actually needs

This is the checklist the repo demonstrates: the things people forget until 3am. Each maps to real files here.

The application itself

  • Config from the environment, validated on boot (src/config/env.ts, Zod); the app refuses to start with bad config instead of failing later
  • Health endpoint (/health) so Kubernetes knows when a pod is alive
  • Graceful shutdown: drains connections on SIGTERM so rolling updates cause zero dropped requests
  • Structured logging shipped off the box (Winston → Loki), not console.log lost inside a dead pod

Observability (you can't operate what you can't see)

  • Metrics exposed at /metrics and scraped by Prometheus
  • Dashboards in Grafana, provisioned as code (monitoring/grafana/), not hand-built and lost
  • Logs aggregated in Loki so you query across pods, not kubectl logs one at a time

Infrastructure (reproducible, not artisanal)

  • Terraform provisions everything on AWS (VPC, EKS, RDS, IAM, ACM, Route 53, Secrets Manager); run once, terraform destroy to reverse
  • Secrets never in git: Kubernetes Secrets / AWS Secrets Manager
  • TLS everywhere: ACM certs terminated at the load balancer

Operations (surviving traffic and bad deploys)

  • Horizontal Pod Autoscaler (2 to 10 pods on CPU); handles spikes automatically
  • Rolling updates with maxUnavailable tuned so deploys don't take the service down
  • One-command rollback (kubectl rollout undo) when a deploy goes wrong
  • Automated CI/CD so deploys are boring and repeatable, not a person running commands

How deployment actually works

Two mental models to keep straight (this trips everyone up at first):

flowchart LR
A["terraform apply<br/><i>run once</i>"] -->|creates the WHERE| A2["AWS<br/>VPC · EKS · RDS · DNS · certs"]
B["bash k8s/deploy.sh<br/><i>run once</i>"] -->|creates the WHAT| B2["Kubernetes<br/>pods · services · HPA · monitoring"]
C["git tag → GitHub Actions<br/><i>every deploy</i>"] -->|ships NEW CODE| C2["test → build → rolling update"]
Loading

Terraform manages AWS. Kubernetes manifests manage your app. Day-to-day you never touch Terraform; you push a tag and CI does a rolling update. You only rerun Terraform to change infrastructure (bigger nodes, new subdomain, K8s version bump).

The CI/CD pipeline (every deploy)

One git tag runs the whole thing. Three jobs, each gated on the last: a bad test never builds, a bad build never deploys. Defined in .github/workflows/backend.yml.

flowchart LR
DEV["bun run release<br/>git tag backend-v*"] --> GH{{"GitHub Actions"}}
subgraph test["1 · test"]
direction TB
T1["bun install"] --> T2["prisma generate + db push<br/>(Postgres 16 service)"] --> T3["tsc typecheck"] --> T4["vitest suite"]
end
subgraph build["2 · build"]
direction TB
B1["extract version"] --> B2["docker buildx<br/>(GHA layer cache)"] --> B3["push to Docker Hub<br/>:latest + :version"]
end
subgraph deploy["3 · deploy"]
direction TB
D1["configure AWS creds"] --> D2["aws eks update-kubeconfig"] --> D3["kubectl set image<br/>rolling update"] --> D4["rollout status<br/>(zero downtime)"]
end
GH --> test
test -->|on pass| build
build -->|on pass| deploy
deploy --> LIVE(["Live on EKS<br/>node-app-cluster · ap-south-1"])
Loading

The production flow, end to end

flowchart TD
DNS["Route 53 DNS<br/>node.gdgrbu.dev · dashboard.gdgrbu.dev"]
LB["AWS Load Balancers<br/><i>ACM SSL termination</i>"]
DNS --> LB
subgraph cluster["EKS · node-app-cluster · ap-south-1 · namespace: node-app"]
BE["Backend<br/>2 to 10 pods (HPA)"]
GRAF["Grafana"]
PROM["Prometheus"]
LOKI["Loki"]
DB["PostgreSQL<br/>EBS in-cluster · or managed RDS"]
PROM -. "scrapes /metrics" .-> BE
BE -. "ships logs" .-> LOKI
GRAF --> PROM
GRAF --> LOKI
BE --> DB
end
LB --> BE
LB --> GRAF
Loading

Try it yourself

1. Run it locally (no cluster)

cd backend
bun install
docker compose up -d # PostgreSQL
bun run db:push
bun run dev # http://localhost:3000

2. Run it on Kubernetes (Minikube, free)

minikube start
bash k8s/deploy.sh
# Backend :30080 · Prometheus :30090 · Grafana :30030 (admin/admin)

This gives you the entire production topology (app, database, Prometheus, Grafana, Loki, autoscaler) on your laptop, at no cost. Best place to learn.

3. Run it on AWS EKS (real, costs money)

cd backend/infra
cp terraform.tfvars.example terraform.tfvars # edit with your values
terraform init && terraform apply # provisions all AWS infra (~15-20 min)
aws eks update-kubeconfig --name node-app-cluster --region ap-south-1
bash k8s/deploy.sh # same manifests as Minikube

💸 EKS is not free. Roughly ~$160-200/month if left running (control plane, nodes, load balancers, NAT, RDS). For learning: spin up, poke around, terraform destroy; a few hours costs under a dollar. Always tear down when done.


Where to go deep

The root README is the map. The detail lives in the backend, which has its own full docs:

DocWhat's in it
backend/README.mdFull backend reference: API, scripts, env vars, project layout
backend/docs/eks-deployment.mdStep-by-step EKS deploy from zero to live API
backend/docs/terraform.mdTerraform IaC: what it creates, when to run it, cost breakdown
backend/docs/aws-eks.mdManual EKS setup plus every error hit and how it was fixed: the real education
backend/docs/commands.mdkubectl / aws cheat sheet

Repo layout

backend/
├── src/ # The application (Bun + Express + Prisma)
├── prisma/ # DB schema + seed
├── k8s/ # Kubernetes manifests, same on Minikube and EKS
│ ├── backend/ # deployment, service, HPA, config, secret
│ ├── postgres/ # database + persistent volume
│ └── monitoring/ # Prometheus, Grafana, Loki
├── infra/ # Terraform, the entire AWS footprint as code
├── monitoring/ # Grafana dashboards + provisioning (as code)
├── docs/ # Deep-dive guides (see table above)
└── Dockerfile # Multi-stage production image

License

Educational use. ISC.

About

Learn to deploy a real backend to Kubernetes on AWS EKS: Terraform, Prometheus + Grafana, and CI/CD, end to end.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages