Deploying to Kubernetes
Deploy the web and API applications to any Kubernetes cluster using the included manifests, deploy script, and verification script
Overview
Stackbase ships a complete set of Kubernetes manifests in the k8s/ directory, plus a deploy.sh script that builds and pushes your Docker images and applies the manifests, and a verify.sh script that checks the rollout afterward.
The manifests are template-aware: a full-stack project gets both web and API resources, while web-only and api-only projects ship only the manifests for the app they include.
What's included
| File | Purpose |
|---|---|
k8s/namespace.yml | Dedicated stackbase namespace |
k8s/configmap.yml | Non-secret config (NODE_ENV; API_INTERNAL_URL for fullstack) |
k8s/api-deployment.yml | API Deployment with resource limits and readiness/liveness probes |
k8s/api-service.yml | ClusterIP Service for the API on port 4000 |
k8s/web-deployment.yml | Web Deployment with resource limits and probes |
k8s/web-service.yml | ClusterIP Service for the web app on port 3000 |
k8s/api-ingress.yml | nginx Ingress routing /api/v1/* → API (with prefix rewrite) |
k8s/web-ingress.yml | nginx Ingress routing / → web |
k8s/api-hpa.yml | HorizontalPodAutoscaler (CPU/memory) for the API |
k8s/web-hpa.yml | HorizontalPodAutoscaler (CPU/memory) for the web app |
deploy.sh | Build → push → apply pipeline (pnpm k8s:deploy) |
k8s/verify.sh | Post-deploy health/rollout checks (pnpm k8s:verify) |
Kubernetes support is opt-in. The scaffolder asks "Include Kubernetes
manifests?" (only when Docker is enabled, since the manifests run the Docker
images). If you decline — or opt out of Docker — the k8s/ directory and
deploy.sh are not generated.
Prerequisites
- A running Kubernetes cluster (cloud-managed, or local via minikube / kind)
kubectlconfigured to talk to your cluster- A container registry account (the scripts assume Docker Hub)
- An ingress-nginx controller installed in the cluster
- metrics-server installed (required for the HPAs to scale)
If your cluster doesn't already have them, install the ingress controller and metrics-server:
# nginx Ingress controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/cloud/deploy.yaml
# Metrics Server (required for HPA — without it the HPAs read <unknown> and cannot scale)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlOn minikube, enable the bundled metrics-server addon instead: minikube addons enable metrics-server.
Step 1: Set your registry username
The deploy script and deployment manifests use the placeholder your-dockerhub-username. Replace it with your own Docker Hub username in:
deploy.sh— theUSERNAMEvariablek8s/api-deployment.yml— theimage:fieldk8s/web-deployment.yml— theimage:field
USERNAME="your-dockerhub-username"image: your-dockerhub-username/your-project-api:latestMake sure you are logged in to your registry locally so the script can push:
docker loginStep 2: Create production environment files
Secrets are created from per-app .env.production files. If you scaffolded with Docker, these were generated for you — fill them in with real production values:
apps/api/.env.productionapps/web/.env.production
deploy.sh turns each into a Kubernetes Secret ([your-project]-api-env, [your-project]-web-env) that the deployments load via envFrom.
Never commit .env.production files. They contain database credentials, auth
secrets, and API keys.
Step 3: Deploy
Run the deploy script from the project root:
pnpm k8s:deployThis will:
- Build the Docker image(s) from
docker-compose.prod.yml - Tag and push them to your registry
- Apply the namespace and ConfigMap
- Create/update the Secrets from your
.env.productionfiles - Apply the Deployments, Services, Ingress, and HPAs
- Wait for each rollout to complete
Step 4: Verify
After deploying, run the verification script:
pnpm k8s:verifyIt works outward — pods → deployments → services/endpoints → in-cluster HTTP → ingress → HPA — and exits non-zero if any hard check fails, so it can be used in CI.
How it works
Autoscaling owns replica count
The Deployments intentionally omit replicas. The per-app HorizontalPodAutoscaler (k8s/api-hpa.yml, k8s/web-hpa.yml) owns that value (min 2, max 10, scaling on CPU/memory). Setting replicas on the Deployment would reset the pod count on every kubectl apply and fight the autoscaler.
Ingress routing
The ingress mirrors the local dev rewrite: requests to /api/v1/* are stripped to /api/* and routed to the API service (k8s/api-ingress.yml), while everything else goes to the web app (k8s/web-ingress.yml).
Internet → nginx Ingress (:80/:443)
├─ /api/v1/* → rewrite /api/v1/* → /api/* → stackbase-api-service (:4000) → Express API
└─ /* → stackbase-web-service (:3000) → Next.js pages
Next.js server-side fetches (SSR) reach the API in-cluster via
API_INTERNAL_URL → stackbase-api-service (:4000), bypassing the ingress.Next.js server-side fetches (SSR) don't go back out through the ingress — they reach the API directly over the cluster network via API_INTERNAL_URL (set in k8s/configmap.yml), pointed at the API's ClusterIP service.
Custom domain and TLS
The manifests ship hostless (catch-all), so they work as-is against the ingress controller's external IP. For a real domain, add a host: to each rule in k8s/api-ingress.yml and k8s/web-ingress.yml.
To enable TLS, install cert-manager, then uncomment the tls: block and the cert-manager.io/cluster-issuer annotation in those ingress files.
Health probes
The API uses /readyz (readiness) and /healthz (liveness); the web app probes /. These match the endpoints exposed by the Express API (/healthz, /readyz, /health).
Useful commands
Inspect the running deployment (replace [your-project] with your namespace):
kubectl get pods -n [your-project]
kubectl get services -n [your-project]
kubectl get ingress -n [your-project]
kubectl get hpa -n [your-project]
kubectl logs -n [your-project] -l app=[your-project]-api
kubectl logs -n [your-project] -l app=[your-project]-webThe API health endpoints (/health, /healthz, /readyz) live at the API root and are not exposed through the ingress, so port-forwarding is how you reach them locally:
# Ctrl+C to stop the tunnel
kubectl port-forward -n [your-project] svc/[your-project]-api-service 4000:4000 # → http://localhost:4000/health
kubectl port-forward -n [your-project] svc/[your-project]-web-service 3000:3000 # → http://localhost:3000Troubleshooting
- HPA
TARGETSshows<unknown>— metrics-server isn't serving metrics. Install it (on minikube:minikube addons enable metrics-server). ImagePullBackOff— the image wasn't pushed or the registry username in the manifests doesn't match what you pushed. Re-check Step 1 anddocker login.- Service has no endpoints — the pods aren't ready; inspect them with
kubectl get pods -n [your-project]andkubectl logs.
Need help? Check the Troubleshooting Guide or open an issue on GitHub.