{"id":51462589,"url":"https://github.com/vellankikoti/code2k8s","last_synced_at":"2026-07-06T07:01:17.504Z","repository":{"id":353065193,"uuid":"1217829025","full_name":"vellankikoti/code2k8s","owner":"vellankikoti","description":"Hands-on guide to deploying Next.js + Postgres + Redis on a bare-metal k3s cluster using three $5 VPS nodes. Covers ingress, cert-manager HTTPS, persistent volumes, and zero-downtime deploys.","archived":false,"fork":false,"pushed_at":"2026-04-22T12:57:32.000Z","size":172,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T08:28:27.839Z","etag":null,"topics":["bare-metal","cert-manager","devops","ingress-nginx","k3s","kubernetes","nextjs","postgres","redis","tutorial"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vellankikoti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T09:00:48.000Z","updated_at":"2026-04-22T12:57:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vellankikoti/code2k8s","commit_stats":null,"previous_names":["vellankikoti/code2k8s"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vellankikoti/code2k8s","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vellankikoti%2Fcode2k8s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vellankikoti%2Fcode2k8s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vellankikoti%2Fcode2k8s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vellankikoti%2Fcode2k8s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vellankikoti","download_url":"https://codeload.github.com/vellankikoti/code2k8s/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vellankikoti%2Fcode2k8s/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35180933,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-06T02:00:07.184Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bare-metal","cert-manager","devops","ingress-nginx","k3s","kubernetes","nextjs","postgres","redis","tutorial"],"created_at":"2026-07-06T07:01:16.026Z","updated_at":"2026-07-06T07:01:17.495Z","avatar_url":"https://github.com/vellankikoti.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Code2K8s — Deploy Next.js + Postgres + Redis to Kubernetes, then poke it\n\n\u003e A hands-on guide to standing up a real k8s stack — with HTTPS, persistent storage, and zero-downtime deploys — and a **live playground** for watching Kubernetes react to load, pod kills, and rolling updates in real time. Nothing invented; every component is a stock install of a public project. Kustomize overlays ship for **killercoda, k3d, Docker Desktop, minikube, bare-metal VPS, EKS, GKE, AKS**.\n\n- **Try it free in the browser** (no install, no card): [killercoda instructions →](docs/platforms.md#start-free-in-the-browser-killercoda)\n- **Live playground demos** — autoscaling, self-healing, cache vs state, rolling updates: [walkthrough →](docs/playground.md)\n- **Run it on 3× $5 VPS**: [bare-metal walkthrough →](docs/bare-metal-k3s-guide.md)\n- **Any other cluster**: [platform overlay matrix →](docs/platforms.md)\n\n---\n\n## What you'll build\n\n```\n       Internet\n           │  (DNS: *.apps.example.com → node-1)\n           ▼\n  ┌────────────────────────────────────────────────────┐\n  │  node-1 (control-plane)                            │   Hetzner CX22  ~€4.50\n  │  node-2, node-3 (workers)                          │   each — $15 total\n  │                                                    │\n  │  k3s  ·  ingress-nginx  ·  cert-manager            │\n  │  local-path-provisioner (bundled)                  │\n  │                                                    │\n  │  namespace \"app\":                                  │\n  │    Deployment   web (Next.js, 2 replicas)          │\n  │    StatefulSet  postgres (10 Gi PVC)               │\n  │    StatefulSet  redis     (2 Gi PVC)               │\n  │    Ingress      app.apps.example.com + TLS         │\n  └────────────────────────────────────────────────────┘\n```\n\n## Why bother\n\n- **$15/month** total — three $5 VPS nodes (Hetzner / DigitalOcean / Contabo / Linode).\n- **Real HTTPS.** Automatic cert renewal via cert-manager + Let's Encrypt. No reverse-proxy config to hand-edit.\n- **Real persistence.** Postgres data survives pod and node restarts.\n- **Zero-downtime rolling deploys.** `maxUnavailable: 0` + readiness probes = no dropped requests when you ship.\n- **No cloud lock-in.** Works identically on any provider that gives you an SSH-able Linux box.\n- **Learn the primitives.** You end up with manifests you could commit and maintain by hand — not a black-box PaaS.\n- **See it behave, not just run.** The demo app has buttons that trigger load, flush cache, and write to the DB — while you watch `kubectl get hpa -w` and `kubectl get pods -w` show the cluster reacting. [Playground scenarios →](docs/playground.md)\n\n## Stack\n\n| Layer | Project | Why |\n|---|---|---|\n| Cluster | [k3s](https://k3s.io) | Single 100 MB binary. SQLite datastore. Fits 2 GB RAM. |\n| Storage | [local-path-provisioner](https://github.com/rancher/local-path-provisioner) | Bundled. Directory-on-disk PVCs. No cloud bill. |\n| Ingress | [ingress-nginx](https://kubernetes.github.io/ingress-nginx/) | Widest ecosystem support — every Helm chart assumes it. |\n| TLS | [cert-manager](https://cert-manager.io) + Let's Encrypt | Fire-and-forget HTTPS via HTTP-01. |\n\n## Quickstart on any cluster (Kustomize)\n\n```bash\ngit clone https://github.com/vellankikoti/code2k8s \u0026\u0026 cd code2k8s\n\n# Auto-detects cluster type from kubectl context + node labels, then asks\n# to confirm. Installs metrics-server and applies the matching overlay.\n./scripts/playground-up.sh            # auto-detect (interactive)\n./scripts/playground-up.sh -y         # auto-detect, no prompt\n./scripts/playground-up.sh killercoda # force a specific overlay\n# overlays: killercoda · k3d · docker-desktop · minikube · bare-metal · eks · gke · aks\n\n# ...or do it by hand:\nkubectl apply -k infra/overlays/\u003cplatform\u003e/\n```\n\nDetection uses `kubectl config current-context` (catches `docker-desktop`, `minikube`, `k3d-*`, `kind-*`) and node `providerID` / labels (EKS → `aws://`, GKE → `gce://`, AKS → `azure://`, k3s → `bare-metal` or `killercoda`). Unknown clusters fall through to a list-and-pick prompt.\n\nWhen the script finishes you'll see a **\"What to do next\"** block with the exact URL/port to open and the four demo scenarios to run. On killercoda that's the **`30080`** tab at the top of the terminal (add it via `+` → *Select port to view on Host 1* → `30080`). On k3d / minikube / Docker Desktop: `kubectl -n app port-forward svc/web 8080:80` and open `http://localhost:8080`.\n\nIf the rollout times out, the script prints a diagnostic snapshot (pods, PVCs, recent events, `describe` of the first web pod) and the two most common fixes — usually a Pending PVC (no default StorageClass) or a still-pulling image. Re-run the script once Postgres is Running.\n\nSee [`docs/platforms.md`](docs/platforms.md) for the per-platform prereqs (ingress controller, cert-manager, storage class). The base manifests in `infra/base/` are cluster-neutral — only the overlays touch platform-specific fields.\n\n## Try it — what to test and how\n\nOnce the dashboard is open (two counters, three buttons, current pod name at the top), run each scenario in a spare terminal while you poke the UI.\n\n### 1. Autoscaling (HPA)\n\n```bash\nkubectl get hpa -n app -w\nkubectl get pods -n app -l app=web -w     # in a second pane\n```\n\nClick **Generate Load** in the browser 3–5 times in quick succession.\n\n**Expected:** HPA `TARGETS` jumps past `50%`; within ~15s `REPLICAS` climbs `1 → 3 → 5`; new pods appear as `Pending → ContainerCreating → Running`. Stops the load and you'll see scale-down begin after a ~60s stabilization window.\n\n### 2. Self-healing\n\n```bash\nkubectl get pods -n app -l app=web -w\nkubectl delete pod -n app -l app=web --force --grace-period=0 | head -1\n```\n\n**Expected:** killed pod goes `Terminating → gone`; the Deployment controller spawns a replacement immediately; the dashboard's pod-name label flips to the surviving/new pod with no visible downtime.\n\n### 3. Cache vs DB (ephemeral vs persistent state)\n\nIn the browser:\n1. Click **Write to DB** a few times — Postgres counter rises.\n2. Click **Generate Load** — Redis counter rises much faster.\n3. Click **Reset Cache**.\n\n**Expected:** Redis counter → `0` instantly; Postgres counter unchanged. Bonus — prove persistence survives pod death:\n\n```bash\nkubectl delete pod -n app postgres-0\nkubectl get pods -n app postgres-0 -w\n```\n\nRefresh the dashboard — Postgres counter is still there (PVC survived).\n\n### 4. Zero-downtime rolling update\n\n```bash\nkubectl get pods -n app -l app=web -w\nkubectl patch deploy/web -n app \\\n  -p \"{\\\"spec\\\":{\\\"template\\\":{\\\"metadata\\\":{\\\"annotations\\\":{\\\"rolled\\\":\\\"$(date +%s)\\\"}}}}}\"\n```\n\n**Expected:** a new pod reaches `Ready` *before* an old one is terminated (`maxUnavailable: 0`, `maxSurge: 1`); `preStop: sleep 20` + readiness probe drain in-flight requests; dashboard never errors.\n\n### Observability cheatsheet\n\n```bash\nkubectl top pods -n app                                     # needs metrics-server\nkubectl logs -n app -l app=web -f --max-log-requests=10     # tail all web pods\nkubectl get events -n app --sort-by=.lastTimestamp | tail   # recent events\nkubectl describe hpa web -n app                             # why HPA did what it did\n```\n\n### Teardown\n\n```bash\nkubectl delete -k infra/overlays/\u003cplatform\u003e/\n```\n\nFull walkthrough with more edge cases: [`docs/playground.md`](docs/playground.md).\n\n## Quickstart on bare-metal (3× $5 VPS)\n\n```bash\n# 1. On node 1 (control plane)\nbash infra/scripts/01-install-k3s-server.sh\n\n# 2. On workers — paste the token from step 1\nK3S_URL=https://\u003cNODE1_IP\u003e:6443 K3S_TOKEN=\u003ctoken\u003e \\\n  bash infra/scripts/02-install-k3s-agent.sh\n\n# 3. Pull the kubeconfig to your laptop\nscp root@\u003cNODE1_IP\u003e:/etc/rancher/k3s/k3s.yaml ~/.kube/code2k8s.yaml\nsed -i '' \"s/127.0.0.1/\u003cNODE1_IP\u003e/\" ~/.kube/code2k8s.yaml\nexport KUBECONFIG=~/.kube/code2k8s.yaml\n\n# 4. Install ingress + TLS\nbash infra/scripts/03-install-ingress-nginx.sh\nACME_EMAIL=you@example.com bash infra/scripts/04-install-cert-manager.sh\n\n# 5. Deploy the demo stack (edit 3 CHANGE-ME placeholders first)\nkubectl apply -f infra/cluster/app-stack.yaml\n```\n\nSee the [full guide](docs/bare-metal-k3s-guide.md) for the explanation behind each line.\n\n## Repo layout\n\n```\ndocs/\n├── bare-metal-k3s-guide.md    ← the in-depth VPS walkthrough\n├── platforms.md               ← per-platform overlay matrix + killercoda instructions\n└── playground.md              ← live k8s demo scenarios (HPA, self-heal, rolling update)\n\ninfra/\n├── base/                      ← cluster-neutral manifests (Deployment, StatefulSets,\n│                                 HPA, Ingress — Kustomize base)\n├── overlays/\n│   ├── killercoda/            ← run free in a browser\n│   ├── k3d/  docker-desktop/  minikube/\n│   ├── bare-metal/\n│   └── eks/  gke/  aks/\n└── scripts/                   ← bootstrap scripts for the bare-metal path\n\nscripts/\n└── playground-up.sh           ← installs metrics-server + applies an overlay\n\nexamples/next-postgres-redis/  ← the demo app: Next.js dashboard + /api endpoints\n                                 (built \u0026 published via GitHub Actions)\n```\n\n## FAQ\n\n**Do I need three nodes?** No — one works for the whole guide. Three makes the ingress / worker separation realistic and gives you a node to lose without everything going dark.\n\n**Why not managed Kubernetes (EKS / GKE / AKS)?** You can. The manifests in `infra/cluster/` run unchanged. This guide is specifically for people who want to see the whole stack end-to-end on hardware they can SSH into.\n\n**Does this work on Raspberry Pi / homelab?** Yes. k3s runs on `arm64` and `armv7`. Swap the images accordingly and everything else is the same.\n\n**Why k3s over full Kubernetes?** Full control-plane pods eat ~1 GB of RAM before anything of yours runs. k3s with SQLite does the same job in ~250 MB. Upgrade when you outgrow it; the YAML is identical.\n\n**Can I use my existing domain?** Yes — set the wildcard A record and edit the two `CHANGE-ME` host names in `infra/cluster/app-stack.yaml`.\n\n**Is this a PaaS / Heroku clone?** No. You don't paste a repo URL and get a deploy. It deploys one specific demo app so you have a real workload to *observe* Kubernetes behavior against — autoscaling, self-healing, rolling updates. If you want repo-to-deploy, look at Coolify, Dokku, or CapRover.\n\n**Will the HPA work on my cluster?** It needs `metrics-server` installed. `./scripts/playground-up.sh` installs it for you; EKS/GKE/AKS have it as an add-on; k3s and Docker Desktop ship it enabled.\n\n## License\n\nMIT. See the guide for what's deliberately out of scope (HA Postgres, monitoring, CI/CD, backups) — each of those is its own rabbit hole.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvellankikoti%2Fcode2k8s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvellankikoti%2Fcode2k8s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvellankikoti%2Fcode2k8s/lists"}