Kubernetes for developers: deploy your first app on a local cluster
Step-by-step tutorial: spin up a local Kubernetes cluster with minikube or kind, deploy an app, expose it with a Service, and add liveness probes plus scaling.
The bottom line
A working Kubernetes deployment on a laptop is achievable in under an hour for a developer who has Docker installed and is comfortable with the terminal. This tutorial spins up a local cluster with either minikube (v1.38.1 as of February 2026 8 ) or kind (v0.31.0 as of December 2025 9 ), deploys a small containerised web app, exposes it with a Service, adds a liveness probe so Kubernetes can self-heal failing pods, and scales the deployment from one replica to three. Every command is verifiable locally; no cloud account is required.
The Kubernetes basics learning path on kubernetes.io defines six canonical modules: create a cluster, deploy an app, explore your app, expose it publicly, scale it up, and update it 2 . This tutorial collapses the same six steps into a single end-to-end loop using a more current example image, plus a probe and an Ingress section the basics path leaves to later chapters.
Production-grade clusters (managed EKS / GKE / AKS), Helm, GitOps with Argo CD, mTLS, and operators are separate articles.
What you’ll build
By the end the reader will have: a running local Kubernetes cluster on their laptop, a Deployment manifest controlling three pod replicas of a containerised web server, a Service exposing those pods on a stable cluster IP, a liveness probe so Kubernetes restarts pods that stop responding, and an Ingress declaration the reader can study even if the local cluster’s ingress addon needs an extra step.
Five YAML files at the end:
deployment.yaml: declares the Deployment + pod template.service.yaml: declares the Service.ingress.yaml: declares the Ingress (for the explainer section).probe-demo.yaml: a standalone pod showing a liveness probe in isolation.kustomization.yaml: optional, but useful for applying all at once.
Prerequisites
Before starting, the reader needs:
- Docker Desktop or Docker Engine installed and running. Both minikube and kind use Docker as the cluster’s container runtime. Confirm with
docker --version. - kubectl installed. The official Kubernetes CLI; on macOS,
brew install kubectl; on Linux, follow the Kubernetes install-tools docs. Confirm withkubectl version --client. - One of: minikube or kind. Both run a single-node Kubernetes cluster inside containers. Pick one; this tutorial covers both.
- A code editor. VS Code, Cursor, or anything with YAML highlighting.
Allow roughly 4 GB of free RAM for the cluster and pods to breathe. A laptop with 8 GB total will work but feel sluggish; 16 GB is comfortable.
Image: Kubernetes official “Hello Minikube” tutorial (kubernetes.io/docs/tutorials/hello-minikube/), used for editorial coverage of the canonical kubectl deploy-and-expose flow this tutorial implements.
The mental model
Kubernetes is a control loop over containers. The reader declares the desired state (“I want three replicas of this image, listening on port 8000, restarted if they crash”); the control plane keeps the cluster matching that state. Every Kubernetes object is a YAML declaration of desired state, applied with kubectl apply -f file.yaml. The cluster reconciles continuously.
Four object types do most of the work for a first app:
- Pod: the smallest deployable unit. One or more containers sharing a network namespace. Pods are ephemeral; a crashed pod is replaced by a new one with a different IP.
- Deployment: a controller that maintains a set of replica pods from a pod template. Deployments handle rolling updates and rollbacks. Almost no one creates pods directly; deployments own the pod lifecycle.
- Service: a stable virtual IP and DNS name routing traffic to whichever pods currently match a label selector. Pods come and go; the Service abstracts that churn.
- Ingress: HTTP / HTTPS routing from outside the cluster to Services, by host and path. Optional; Services alone can expose apps within a dev cluster.
Probes (liveness, readiness, startup) are per-container settings inside the pod template, not separate objects.
Step 1: Start the cluster
Pick minikube or kind. Both work; the differences are minor for a first deploy.
Option A: minikube. Install (macOS Homebrew shown; other platforms covered on the minikube install docs):
brew install minikube
minikube start
minikube start provisions a single-node cluster inside a Docker container and writes a kubeconfig entry so kubectl talks to the cluster automatically. First start downloads the Kubernetes images and takes 2–4 minutes. Subsequent starts are 30 seconds.
Option B: kind. Install via Homebrew (macOS) or go install sigs.k8s.io/kind@v0.31.0 9 :
brew install kind
kind create cluster
kind create cluster produces a single-node cluster (also a Docker container) named kind by default. The kubeconfig context is set automatically.
Verify either cluster:
kubectl get nodes
A single Ready node should appear within a few seconds. If kubectl reports “connection refused”, the cluster failed to start; re-run with minikube start --alsologtostderr or kind create cluster --verbosity 2 to see the underlying error.
The Kubernetes release line shipping on these tools is the current stable 1.36.x train, with 1.36.1 released on 13 May 2026 7 .
Step 2: A first Deployment
Paste into deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
labels:
app: hello-web
spec:
replicas: 3
selector:
matchLabels:
app: hello-web
template:
metadata:
labels:
app: hello-web
spec:
containers:
- name: hello-web
image: nginx:1.27
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
The structure mirrors the canonical Deployment example in the Kubernetes docs 3 : apiVersion: apps/v1, kind: Deployment, three replicas, a matchLabels selector that ties the Deployment to the pods it creates via the matching labels in the pod template. The nginx:1.27 image is a placeholder web server; the reader can swap in any image, including the one built in a separate Docker tutorial.
Three pieces beyond the basic shape are worth flagging:
- Probes.
livenessProbetells Kubernetes how to tell if the container is dead and needs a restart;readinessProbetells Kubernetes if it’s ready to accept traffic. Both follow the schema the Kubernetes probe docs canonicalise 6 :httpGetagainst a path and port, withinitialDelaySecondsandperiodSecondscontrolling timing. - Resource requests and limits.
requestsis what the scheduler reserves;limitsis the ceiling enforced at runtime. Without them a pod can starve the node. - Labels. The Deployment labels its pods
app: hello-webso the Service can find them.
Apply it:
kubectl apply -f deployment.yaml
kubectl get deployments
kubectl get pods
Within 20–60 seconds three hello-web-<hash> pods should appear in Running state. If they sit in ContainerCreating for more than a minute, kubectl describe pod <pod-name> shows the event log; image-pull errors are the most common cause on a slow connection.
Step 3: Expose with a Service
Paste into service.yaml:
apiVersion: v1
kind: Service
metadata:
name: hello-web
spec:
type: NodePort
selector:
app: hello-web
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
The Service’s selector matches the Deployment’s pod labels; port is what the Service exposes; targetPort is the port on the pod the traffic forwards to. The Kubernetes Service docs canonicalise four type values: ClusterIP (default, internal only), NodePort (exposed on every node’s IP at a static port), LoadBalancer (cloud-provider load balancer), and ExternalName (CNAME alias) 4 . For a local cluster, NodePort or LoadBalancer (which minikube can fake) both work; NodePort is the simplest cross-tool choice.
Apply and inspect:
kubectl apply -f service.yaml
kubectl get service hello-web
The output shows a NodePort like 30000-32767. To reach the service on minikube:
minikube service hello-web --url
On kind, port-forward from the host:
kubectl port-forward service/hello-web 8080:80
Then curl http://localhost:8080 should return nginx’s default page. If port-forward exits immediately with “no endpoints available”, the pods aren’t Ready yet; re-check kubectl get pods.
Image: Kubernetes official Deployment concept reference (kubernetes.io/docs/concepts/workloads/controllers/deployment/), used for editorial coverage of the Deployment schema this tutorial implements.
Step 4: Watch probes in action
To see a liveness probe restart a failing container, paste into probe-demo.yaml:
apiVersion: v1
kind: Pod
metadata:
name: liveness-demo
labels:
app: liveness-demo
spec:
containers:
- name: liveness
image: busybox:1.36
args:
- /bin/sh
- -c
- "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
The probe-demo pattern is the canonical exec-probe example from the Kubernetes probe docs 6 : the container creates /tmp/healthy, deletes it after 30 seconds, then sleeps. The livenessProbe runs cat /tmp/healthy every five seconds. Once the file disappears, the probe fails, and Kubernetes restarts the container after the default failure threshold.
Apply and watch:
kubectl apply -f probe-demo.yaml
kubectl get pod liveness-demo --watch
The RESTARTS count climbs by one roughly every minute. That’s the control loop working; the cluster is enforcing the declared liveness contract without operator action.
Step 5: Scale up and roll out
Scale the original Deployment from three replicas to five:
kubectl scale deployment hello-web --replicas=5
kubectl get pods
Two new pods appear within seconds. The Deployment controller created them to match the new desired state.
A rolling update is the same pattern: change the image in deployment.yaml (say, from nginx:1.27 to nginx:1.28) and re-apply. Kubernetes brings up new pods in batches, waits for each batch to become Ready, and terminates old pods only after the replacement is healthy. The maxSurge and maxUnavailable fields under spec.strategy.rollingUpdate control the batch size; the defaults (25% / 25%) work for most apps.
Roll back if a deploy goes wrong:
kubectl rollout undo deployment hello-web
kubectl rollout status deployment hello-web
Step 6: Ingress (for context)
A NodePort or port-forward is fine on a laptop. Production traffic usually arrives through an Ingress, which routes by host and path to one or more Services. Paste into ingress.yaml for reference:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-web
spec:
ingressClassName: nginx
rules:
- host: hello.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-web
port:
number: 80
The apiVersion: networking.k8s.io/v1, pathType, and backend.service fields follow the schema the Kubernetes Ingress docs canonicalise 5 . To actually route traffic via Ingress on a local cluster, an Ingress controller has to be running. On minikube: minikube addons enable ingress installs the nginx Ingress controller. On kind: the kind Ingress docs walk through a deployment manifest the user has to apply manually. Either way, kubectl apply -f ingress.yaml only registers the routing rule; the controller does the actual proxying.
Worth noting the Kubernetes project itself recommends Gateway API as the forward path for new HTTP routing work; Ingress remains supported but is no longer being developed 5 .
Image: Kubernetes official Service concept reference (kubernetes.io/docs/concepts/services-networking/service/), used for editorial coverage of the Service types this tutorial walks through.
Things that commonly go wrong
A short list of things that bite first-timers, with the fix for each:
kubectl get nodesshows nothing or hangs. The kubeconfig context is wrong.kubectl config current-contextshould showminikubeorkind-kind; switch withkubectl config use-context <name>.- Pods stuck in
ImagePullBackOff. The image name is wrong, the image is private without pull secrets, or the laptop is offline.kubectl describe pod <pod>shows the exact pull error in the Events section. CrashLoopBackOff. The container starts and exits with non-zero.kubectl logs <pod> --previousshows the previous run’s stdout / stderr.Pendingforever. No node has enough CPU or memory.kubectl describe pod <pod>reportsFailedSchedulingwith the resource shortfall. Lowerresources.requestsor restart the cluster with more allocation:minikube start --cpus=4 --memory=8192.- Service has no endpoints. The Service selector doesn’t match any pod labels.
kubectl get endpoints <service>should list pod IPs; if empty, re-check label keys and values. - Ingress returns 404. No Ingress controller is running, or the host header doesn’t match. On a laptop, add
127.0.0.1 hello.localto/etc/hostsandcurl -H "Host: hello.local" http://localhostonce the controller is up.
Clean up
When done, free the laptop’s resources:
kubectl delete -f deployment.yaml -f service.yaml -f ingress.yaml -f probe-demo.yaml
minikube delete
# or
kind delete cluster
kubectl delete removes the application objects; minikube delete or kind delete cluster tears down the cluster itself.
What was deliberately skipped
This tutorial covers the smallest end-to-end loop. A production-grade workflow would also include: ConfigMaps and Secrets for configuration and credentials, namespaces for multi-tenancy, NetworkPolicies for pod-level firewalling, HorizontalPodAutoscaler for traffic-driven scaling, persistent volumes for stateful workloads, RBAC for fine-grained permissions, and a service-mesh layer (Istio, Linkerd) when inter-service traffic gets complex. Helm or Kustomize replaces hand-edited YAML once more than two or three environments need to share a base manifest. GitOps (Argo CD, Flux) replaces kubectl apply once the manifests live in a Git repo a controller reconciles automatically.
None of these change the mental model; they’re additional declarative objects layered on the same control-loop foundation.
Image: Kubernetes official “Configure Liveness, Readiness and Startup Probes” docs (kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/), used for editorial coverage of the probe schema this tutorial walks through.
Recap
A working Kubernetes cluster runs locally with one command (minikube start or kind create cluster). A Deployment + Service plus a liveness probe is the minimum viable shape for a self-healing web app. Scaling is a one-line kubectl scale; rollouts and rollbacks are handled by the Deployment controller. Ingress and Gateway API are the next layer once a Service alone isn’t enough. The Kubernetes basics learning path on kubernetes.io 2 is the canonical reference for the six-step progression this tutorial collapses; the Deployment 3 , Service 4 , and probe 6 concept references hold the full schema for each object type.
How this article was made: an autonomous AI pipeline researched, drafted, fact-checked, and reviewed this piece, aggregating publicly-available information from the sources consulted below. AI (artificial intelligence) can make mistakes, so please cross-check the consulted sources before acting on anything here. Neural Tech Daily is not liable for decisions or outcomes based on this article.
Sources consulted
Cited Sources
- 1. Kubernetes — official Hello Minikube tutorial (canonical kubectl create deployment + kubectl expose + minikube service flow) (accessed ) ↩
- 2. Kubernetes — Learn Kubernetes Basics (the canonical six-module learning path: create cluster, deploy, explore, expose, scale, update) (accessed ) ↩
- 3. Kubernetes — Deployment concept reference (canonical apiVersion apps/v1 schema with replicas, selector matchLabels, template) (accessed ) ↩
- 4. Kubernetes — Service concept reference (canonical ClusterIP, NodePort, LoadBalancer, ExternalName Service types and YAML schema) (accessed ) ↩
- 5. Kubernetes — Ingress concept reference (canonical networking.k8s.io/v1 schema, pathType, ingressClassName, plus Gateway API forward-direction note) (accessed ) ↩
- 6. Kubernetes — Configure Liveness, Readiness and Startup Probes (canonical httpGet + exec probe schema with initialDelaySeconds and periodSeconds) (accessed ) ↩
- 7. Kubernetes — Releases page (current stable v1.36.1 as of 13 May 2026) (accessed ) ↩
- 8. kubernetes/minikube — official GitHub repository (minikube v1.38.1 latest release, 19 Feb 2026, runs Kubernetes locally on macOS / Linux / Windows) (accessed ) ↩
- 9. kubernetes-sigs/kind — official GitHub repository (kind v0.31.0 latest release, 18 Dec 2025, runs Kubernetes in Docker container nodes) (accessed ) ↩
Anonymous · no cookies set