Kubernetes for developers: a complete beginner's guide
By Flavio Copes
Learn what Kubernetes is and how Pods, Deployments, Services, YAML manifests, scaling, updates, and self-healing work in a local cluster.
Kubernetes runs and coordinates containerized applications across a group of machines.
You give Kubernetes a container image and describe how the application should run.
Kubernetes starts it, keeps it running, replaces failed instances, scales it, and helps other applications find it over the network.
That is the short version.
The longer version includes clusters, nodes, Pods, Deployments, Services, labels, selectors, manifests, namespaces, volumes, ConfigMaps, and many other names.
This vocabulary makes Kubernetes look impossible from the outside.
We do not need to learn everything at once.
In this guide we’ll focus on the small set of ideas a developer needs to understand Kubernetes. Then we’ll run an application in a local cluster and see those ideas in practice.
What problem does Kubernetes solve?
Suppose you have packaged a web application in a Docker image.
You can run it with one command:
docker run -p 8080:80 my-web-app
This is enough for local development and many small deployments.
Now imagine the application receives more traffic.
You want to run five copies. They may run on several servers. Traffic must reach a healthy copy. If one process crashes, another one should replace it. When you release a new version, users should not see downtime.
You also need to answer questions like these:
- Which server has room for the next container?
- How do containers find each other?
- What happens when a server fails?
- How do we roll out a new image?
- How do we scale from five copies to ten?
- How do we provide configuration and secrets?
You can write scripts for all of this.
But you would be building a container orchestration system.
Kubernetes is that system.
What does container orchestration mean?
An orchestrator coordinates many pieces so they work together.
Kubernetes does not build your application. It expects a container image that can already run.
It decides where to run containers, watches them, connects them, and reacts when reality differs from your configuration.
You might tell Kubernetes:
Run three copies of this image and make them reachable through one stable name.
Kubernetes handles the ongoing work needed to keep that statement true.
Do you need Kubernetes?
Most applications do not need Kubernetes at the beginning.
A single server, a managed application platform, or a simpler container service may be easier and cheaper.
Kubernetes becomes useful when you have enough services, machines, traffic, or organizational needs to justify a common orchestration layer.
It can give a team:
- a consistent way to run containers
- automatic replacement of failed workloads
- horizontal scaling
- controlled application updates
- service discovery
- a shared API for many infrastructure providers
But Kubernetes also adds operational work.
Someone must maintain the cluster, upgrades, access rules, networking, storage, observability, and costs. A managed Kubernetes service removes some of that work, not all of it.
My advice is simple: learn Kubernetes because the ideas are useful. Adopt it for a project only when the project needs it.
I also built a do you need Kubernetes? quiz if you are making that decision for a real project.
Kubernetes and Docker are different things
Docker helps you build and run containers.
Kubernetes coordinates containers across a cluster.
They are not competitors.
A common workflow looks like this:
- You write an application.
- A Dockerfile describes its container image.
- CI builds the image and pushes it to a registry.
- Kubernetes pulls the image and runs it.
Kubernetes can use container runtimes other than Docker Engine. The important input is the container image format, not the Docker desktop application.
If containers are new to you, read my introduction to Docker first.
The first Kubernetes mental model
Kubernetes works through desired state.
You describe what you want. Kubernetes continually compares that desired state with the current state.
If the two differ, Kubernetes tries to correct the difference.
Suppose you request three copies of a web application.
Kubernetes starts three copies.
If one disappears, the current state becomes two. Kubernetes notices and starts a replacement.
This loop is called reconciliation.
You will hear the word controller often in Kubernetes. A controller watches one kind of desired state and works to make it real.
You do not need to understand controller internals now.
Remember this:
Kubernetes is always trying to make the current state match the desired state.
What is a Kubernetes cluster?
A cluster is the complete Kubernetes environment.
It has two main parts:
- the control plane manages the cluster
- one or more worker nodes run applications
For learning, both parts can run on your computer.
In production, they usually run across several machines.
The official component overview contains the full architecture. We only need a simple view.
What does the control plane do?
The control plane is the brain of the cluster.
Its API receives requests. Its scheduler chooses where workloads run. Its controllers watch the desired state and correct differences. Its data store keeps cluster information.
The main components are:
- API server: the front door to Kubernetes
- scheduler: chooses a node for each new Pod
- controller manager: runs reconciliation loops
- etcd: stores the cluster’s data
When you run a kubectl command, you normally talk to the API server.
You do not connect to a worker node and start a container by hand.
What is a worker node?
A node is a machine where application workloads can run.
It may be a physical server or a virtual machine.
Each node has a few important components:
- a kubelet that communicates with the control plane
- a container runtime that runs containers
- networking components that route traffic
The scheduler assigns a Pod to a node. The kubelet on that node makes sure its containers run.
As an application developer, you usually work with Kubernetes objects rather than individual nodes.
What is a Kubernetes object?
A Kubernetes object is a record stored through the Kubernetes API.
It represents something you want the cluster to manage.
Examples include:
- Pod
- Deployment
- Service
- ConfigMap
- Secret
- Namespace
You usually describe objects in YAML files called manifests.
Here is a small Pod manifest:
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx:stable-alpine
Most manifests have four important fields:
apiVersionselects the Kubernetes API versionkindselects the type of objectmetadataidentifies the objectspecdescribes the desired state
The exact fields inside spec depend on the object type.
Kubernetes also maintains a status. The spec says what you want. The status says what currently exists.
What is a Pod?
A Pod is the smallest deployable unit in Kubernetes.
A Pod contains one or more containers that run together. Those containers share networking and can share storage.
Most application Pods contain one main container.
If two containers must always run together and communicate through localhost, putting them in one Pod can make sense. This is less common than a single-container Pod for beginner applications.
A Pod is not a tiny permanent server.
Pods are replaceable.
Their names and IP addresses may change. Kubernetes can delete one and create another while keeping the application available.
This is why you rarely create standalone Pods for production applications.
You normally create a Deployment that manages Pods for you.
What is a Deployment?
A Deployment manages a set of Pods for an application.
You describe a container image and the number of copies you want. The Deployment creates and updates the Pods.
Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:stable-alpine
This asks for three Pods.
The template is the recipe used to create each Pod.
The Deployment also helps with updates. When the Pod template changes, it can gradually replace old Pods with new ones.
For a stateless web application, a Deployment is usually the first workload object to learn.
What is a Service?
Pods are temporary. Their names and IP addresses can change.
This creates a networking problem.
How does a frontend find a backend if the backend Pods keep changing?
A Kubernetes Service gives a group of Pods a stable network endpoint.
The Service finds its Pods through labels.
Example:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
The selector matches Pods carrying the label app: web.
Traffic sent to the Service can be routed to one of those Pods.
The Pods may change. The Service name stays stable.
This is the key distinction:
- a Deployment keeps the right Pods running
- a Service gives those Pods a stable network identity
Labels and selectors
Labels are key-value pairs attached to Kubernetes objects.
Example:
metadata:
labels:
app: web
environment: development
A selector finds objects with matching labels.
Deployments use selectors to identify their Pods. Services use selectors to identify the Pods that should receive traffic.
Labels connect Kubernetes objects without hard-coding Pod names.
This is powerful, but it creates a common beginner bug.
If a Service selector does not match the Pod labels, the Service has nowhere to send traffic.
When a Service does not work, compare its selector with the labels on the Pods.
Install the local tools
We will use two tools:
kubectlis the Kubernetes command-line clientminikubecreates a local Kubernetes cluster
Follow the official guides to install kubectl and install minikube.
You also need a supported container or virtual machine driver. Docker Desktop is a convenient choice on macOS and Windows.
Check the commands:
kubectl version --client
minikube version
Now start a local cluster:
minikube start
This can take a few minutes the first time.
Check its status:
minikube status
You should see that the host, kubelet, and API server are running.
What is kubectl?
kubectl sends requests to the Kubernetes API.
The command name is commonly pronounced “kube control” or “kube C T L”. Both are understood.
Check the current cluster connection:
kubectl cluster-info
List the nodes:
kubectl get nodes
Your minikube cluster normally has one node.
The local cluster is smaller than a production cluster, but it uses the same core Kubernetes API.
Create your first Deployment
The fastest way to create a Deployment is with a command.
Run the example from the current Hello Minikube tutorial:
kubectl create deployment hello-node \
--image=registry.k8s.io/e2e-test-images/agnhost:2.53 \
-- /agnhost netexec --http-port=8080
This creates a Deployment named hello-node.
The Deployment asks Kubernetes to run a container from the provided image.
List Deployments:
kubectl get deployments
You should see output similar to this:
NAME READY UP-TO-DATE AVAILABLE AGE
hello-node 1/1 1 1 30s
Now list Pods:
kubectl get pods
You should see one Pod with a generated name:
NAME READY STATUS RESTARTS AGE
hello-node-6c9b8b8d7c-k2m5p 1/1 Running 0 30s
Your generated name will be different.
The Deployment owns the Pod. You asked for a Deployment, and its controller created the Pod needed to satisfy the desired state.
Inspect the Deployment
Run:
kubectl describe deployment hello-node
describe shows details and recent events.
It is one of the most useful commands when something does not start.
You can also inspect the Pod:
kubectl describe pod <pod-name>
Replace <pod-name> with the name from kubectl get pods.
Near the end, look at the Events section. Kubernetes records image pulls, scheduling decisions, restarts, and failures there.
Read application logs
Get the Pod name:
kubectl get pods
Then read its logs:
kubectl logs <pod-name>
For a Deployment with one container per Pod, you can also run:
kubectl logs deployment/hello-node
Logs are often your first stop when the container starts but the application fails.
If the Pod has several containers, add -c followed by the container name.
Expose the application
The Pod currently has a cluster-internal IP address.
Create a Service:
kubectl expose deployment hello-node \
--type=LoadBalancer \
--port=8080
List Services:
kubectl get services
On a cloud platform, a LoadBalancer Service can request an external load balancer from the provider.
On minikube, open the Service with:
minikube service hello-node
The command opens the application in your browser.
The Service provides the stable endpoint. It forwards traffic to a matching Pod behind it.
Scale the application
Change the Deployment from one Pod to three:
kubectl scale deployment hello-node --replicas=3
Watch the Pods:
kubectl get pods --watch
Kubernetes starts two more Pods.
Press Ctrl+C to stop watching.
The Service still has one stable identity. It can send requests to any of the three matching Pods.
Scaling down is the same operation:
kubectl scale deployment hello-node --replicas=1
Kubernetes removes the extra Pods.
See self-healing in action
Make sure the Deployment wants one Pod:
kubectl get deployment hello-node
List the Pod:
kubectl get pods
Delete that Pod:
kubectl delete pod <pod-name>
Now list Pods again:
kubectl get pods
The deleted Pod disappears, and the Deployment creates a replacement.
You did not tell Kubernetes to create another Pod.
The desired state still said one replica. The current state briefly said zero. The Deployment controller reconciled the difference.
This small experiment explains much of Kubernetes.
Imperative commands versus YAML manifests
So far we used commands such as kubectl create and kubectl scale.
This style is called imperative. We told Kubernetes to perform an action.
Imperative commands are useful for learning, debugging, and quick experiments.
For maintained applications, you will usually store YAML manifests in Git and use kubectl apply.
The manifest describes the desired state. This is the declarative style.
It gives you code review, history, and repeatability.
Let’s build the same kind of application using a file.
Create a declarative application manifest
First remove the objects from the command-line example:
kubectl delete service hello-node
kubectl delete deployment hello-node
Create a file named app.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
spec:
replicas: 2
selector:
matchLabels:
app: hello-web
template:
metadata:
labels:
app: hello-web
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-web
spec:
selector:
app: hello-web
ports:
- port: 80
targetPort: 80
The --- line separates two Kubernetes objects in one YAML file.
The first object is a Deployment. It creates two Nginx Pods.
The second object is a Service. Its selector matches the app: hello-web label on those Pods.
Apply the file:
kubectl apply -f app.yaml
Kubernetes reports that it created the Deployment and Service.
Check them together:
kubectl get deployments,pods,services
Access the declarative application
The Service uses the default type, ClusterIP.
It is reachable inside the cluster, but not directly from your computer.
For local development, forward a port:
kubectl port-forward service/hello-web 8080:80
Keep that command running and open http://localhost:8080.
Your browser connects to local port 8080. kubectl forwards the connection to port 80 on the Service.
Press Ctrl+C when you finish.
Port forwarding is convenient for local inspection. It is not how you normally expose a production application.
Change the desired state
Open app.yaml and change:
replicas: 2
to:
replicas: 4
Apply the file again:
kubectl apply -f app.yaml
Now check the Pods:
kubectl get pods
Kubernetes creates two more Pods.
The same file created the application and changed it. Git can show that the desired replica count changed from two to four.
Update the container image
Change the image in app.yaml:
image: nginx:1.27-alpine
Apply it:
kubectl apply -f app.yaml
Watch the rollout:
kubectl rollout status deployment/hello-web
The Deployment gradually creates Pods using the new template and removes the old Pods.
List the Pods during an update:
kubectl get pods --watch
This is a rolling update.
Kubernetes Deployments support rollout history and rollback commands, but you do not need to master them yet. First understand that changing the Pod template creates a new Deployment revision.
Why image tags matter
An image tag such as latest can point to different content over time.
That makes deployments harder to reproduce.
Use a specific version tag or immutable image digest for real releases.
For example:
image: nginx:1.27-alpine
is clearer than:
image: nginx:latest
Your own CI system can tag images with a release version or Git commit.
How Kubernetes knows an application is healthy
A running process is not always a working application.
It may accept no traffic because it is still starting. It may be stuck while the process remains alive.
Kubernetes supports health checks called probes:
- a startup probe checks whether the application has started
- a readiness probe checks whether it should receive traffic
- a liveness probe checks whether it should be restarted
Here is a simple readiness probe for Nginx:
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 2
periodSeconds: 5
This block belongs inside a container definition.
If the check fails, a Service stops sending new traffic to that Pod.
Do not copy random probe values into production. The correct timing depends on how your application starts and behaves.
Requests and limits
Kubernetes needs to know how much CPU and memory a container expects.
A request helps the scheduler choose a node with enough room.
A limit caps how much of a resource the container can use.
Example:
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 128Mi
100m means one tenth of a CPU core.
This block also belongs inside the container definition.
Resource settings are important in shared clusters. Poor values can waste capacity or cause unstable applications.
For your first local experiment, it is fine to leave them out.
What is a Namespace?
A Namespace groups Kubernetes objects inside a cluster.
Your first objects go into the default Namespace.
List Namespaces:
kubectl get namespaces
You can create one for an application or environment:
kubectl create namespace demo
Then list Pods inside it:
kubectl get pods -n demo
Namespaces help organize names and access policies.
They are not separate clusters. Cluster-wide resources and shared infrastructure still exist outside Namespace boundaries.
You can stay in the default Namespace while learning.
What is a ConfigMap?
A ConfigMap stores non-secret configuration.
Examples include feature flags, log levels, or a configuration file.
You can expose ConfigMap values to a container through environment variables or mounted files.
This separates configuration from the container image.
You can use the same image in development and production while providing different settings.
Do not use a ConfigMap for passwords or API keys.
What is a Secret?
A Kubernetes Secret stores sensitive configuration such as a password or token.
The name can give a false sense of security.
Secret values are usually base64-encoded in YAML, not encrypted by that encoding. Anyone who can read the Secret can decode it.
A production cluster needs appropriate access control and encryption at rest. Many teams also connect Kubernetes to an external secret manager.
For a beginner, remember two rules:
- do not put plaintext secrets in Git
- access to Kubernetes Secrets must be restricted
What about databases and storage?
Containers and Pods are replaceable.
Application data often must survive replacement.
Kubernetes supports persistent storage through PersistentVolumes and PersistentVolumeClaims.
It also provides StatefulSets for workloads that need stable identities or ordered behavior.
Those features are useful, but running a reliable production database involves backups, replication, upgrades, monitoring, and recovery procedures.
Do not move a database into Kubernetes just because you learned how to run a Deployment.
A managed database outside the cluster is often a better starting point.
How traffic reaches an application
There are several networking layers, but we can keep the first model simple.
Inside the cluster:
- A Pod runs the application.
- A Service gives matching Pods a stable endpoint.
- Other applications connect to the Service name.
For traffic coming from the internet, a cluster may use:
- a Service with type
LoadBalancer - an Ingress controller
- a Gateway API implementation
These depend on the cluster and cloud provider.
Start with Services. Learn Ingress or Gateway when you need to expose several real applications.
The main Service types
You will see these Service types:
ClusterIPis reachable inside the cluster and is the defaultNodePortopens a port on every nodeLoadBalancerasks the environment for an external load balancerExternalNamemaps a Service name to an external DNS name
Most internal services use ClusterIP.
Public exposure depends on your platform. A LoadBalancer Service on minikube behaves differently from one on AWS or Google Cloud.
The Kubernetes API defines the request. The environment provides the implementation.
A request through the cluster
Let’s follow one request to our example.
Your browser connects to the local forwarded port.
kubectl port-forward carries the connection into the cluster and targets the hello-web Service.
The Service has a selector:
selector:
app: hello-web
Kubernetes tracks the ready Pods with that label.
The connection reaches one of those Pods on targetPort: 80.
Nginx inside the Pod responds.
If Kubernetes replaces that Pod, the Service can route later connections to another matching Pod.
The browser does not need to know any Pod name or IP address.
How to debug a failing application
Kubernetes errors feel overwhelming when you inspect everything at once.
Use a small sequence.
First list the objects:
kubectl get deployments,pods,services
Then inspect the failing Pod:
kubectl describe pod <pod-name>
Read its logs:
kubectl logs <pod-name>
Check recent events:
kubectl get events --sort-by=.metadata.creationTimestamp
Common statuses give useful hints:
Pendingmay mean the scheduler cannot place the PodImagePullBackOffmeans Kubernetes cannot pull the imageCrashLoopBackOffmeans the container keeps failing and restartingRunningmeans the process is running, not necessarily that the app is ready
If networking fails, inspect the Service:
kubectl describe service hello-web
Then check whether its selector matches the Pod labels:
kubectl get pods --show-labels
This workflow solves many beginner problems.
Useful kubectl commands
List resources:
kubectl get pods
kubectl get deployments
kubectl get services
Get more information:
kubectl get pods -o wide
kubectl describe pod <pod-name>
Read logs:
kubectl logs <pod-name>
Follow logs:
kubectl logs -f <pod-name>
Apply a manifest:
kubectl apply -f app.yaml
Delete objects from a manifest:
kubectl delete -f app.yaml
Wait for a rollout:
kubectl rollout status deployment/hello-web
Forward a local port:
kubectl port-forward service/hello-web 8080:80
These commands are enough for your first experiments.
Clean up the example
Delete the objects declared in app.yaml:
kubectl delete -f app.yaml
Check that they are gone:
kubectl get deployments,pods,services
The built-in kubernetes Service remains. It represents the Kubernetes API.
Stop the local cluster:
minikube stop
This keeps the cluster so you can start it again later.
To remove the local cluster completely, run:
minikube delete
Kubernetes and Terraform
Terraform and Kubernetes both use declarative configuration, but they usually manage different layers.
Terraform is good at creating infrastructure such as:
- virtual networks
- Kubernetes clusters
- node pools
- databases
- DNS records
Kubernetes manages workloads inside the cluster:
- Deployments
- Services
- ConfigMaps
- application Pods
Terraform can also manage Kubernetes objects through its Kubernetes provider.
That can be useful, but do not assume one tool must own everything. Many teams use Terraform for the cluster and Kubernetes manifests or Helm for application releases.
Clear ownership matters more than using the fewest tools.
Kubernetes and serverless platforms
Serverless platforms hide most infrastructure management.
You deploy a function or application, and the platform handles much of the scheduling, scaling, and machine maintenance.
Kubernetes gives you more control and a more portable API, but you also take on more responsibility.
If a managed platform meets your needs, its constraints can be a benefit.
Do not choose Kubernetes only because it sounds more professional.
Common beginner mistakes
Creating standalone Pods
A standalone Pod is not automatically recreated by a Deployment.
Use a Deployment for normal stateless applications.
Treating Pods like servers
Do not depend on one Pod name, IP address, or local filesystem lasting forever.
Pods are replaceable.
Using latest for every image
Mutable tags make it difficult to know which code is running.
Use versioned tags or image digests for releases.
Forgetting labels and selectors
A Service with the wrong selector will not find your Pods.
Compare the Service selector with kubectl get pods --show-labels.
Editing live objects without updating Git
Manual changes can solve an emergency, but the stored manifest remains the desired source for the team.
Update the file or another deployment may undo your fix.
Giving every container unlimited resources
Without realistic requests, the scheduler cannot make good placement decisions.
Learn resource requests before moving shared workloads to production.
Putting secrets directly in YAML
Base64 is not encryption.
Do not commit production secrets to Git.
Running a database before understanding storage
Stateless examples are forgiving. Databases are not.
Use a managed database or learn the operational requirements first.
Learning every Kubernetes object before deploying anything
Start with Deployment, Pod, Service, labels, and kubectl.
Add concepts when a real need appears.
What we deliberately skipped
Kubernetes has many important production topics:
- role-based access control
- network policies
- autoscaling
- Ingress and Gateway API
- persistent storage
- StatefulSets
- admission controllers
- custom resources and operators
- service meshes
- multi-cluster systems
You do not need these to understand the foundation.
Trying to learn them all at once hides the simple loop at the center of Kubernetes.
A useful Kubernetes mental model
Remember these relationships:
- A cluster contains the whole system.
- Nodes provide machines where workloads run.
- A Pod runs one or more closely related containers.
- A Deployment keeps the requested Pods running and updates them.
- A Service gives matching Pods a stable network endpoint.
- Labels and selectors connect those objects.
kubectltalks to the Kubernetes API.- Controllers keep actual state close to desired state.
That is enough to read many basic Kubernetes configurations.
Where to go next
Repeat the local exercise with one of your own container images.
Create a Deployment. Add a Service. Scale it. Delete a Pod. Change the image. Read the logs.
Then add a readiness probe and resource requests.
Do not start with a complex production cluster.
The most useful first lesson is not how to configure every Kubernetes feature.
It is learning to see the system as a collection of desired objects that controllers keep alive.
Once that idea clicks, the vocabulary becomes much easier.