# Kubernetes for developers: a complete beginner's guide

> Learn what Kubernetes is and how Pods, Deployments, Services, YAML manifests, scaling, updates, and self-healing work in a local cluster.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-07-31 | Topics: [Kubernetes](https://flaviocopes.com/tags/kubernetes/) | Canonical: https://flaviocopes.com/kubernetes-for-developers/

[Kubernetes](https://kubernetes.io/docs/concepts/overview/) 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:

```bash
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?](https://flaviocopes.com/tools/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:

1. You write an application.
2. A Dockerfile describes its container image.
3. CI builds the image and pushes it to a registry.
4. 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](https://flaviocopes.com/docker-introduction/) 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](https://kubernetes.io/docs/concepts/overview/components/) 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:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: nginx
      image: nginx:stable-alpine
```

Most manifests have four important fields:

- `apiVersion` selects the Kubernetes API version
- `kind` selects the type of object
- `metadata` identifies the object
- `spec` describes 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:

```yaml
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:

```yaml
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:

```yaml
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:

- `kubectl` is the Kubernetes command-line client
- `minikube` creates a local Kubernetes cluster

Follow the official guides to [install kubectl](https://kubernetes.io/docs/tasks/tools/) and [install minikube](https://minikube.sigs.k8s.io/docs/start/).

You also need a supported container or virtual machine driver. Docker Desktop is a convenient choice on macOS and Windows.

Check the commands:

```bash
kubectl version --client
minikube version
```

Now start a local cluster:

```bash
minikube start
```

This can take a few minutes the first time.

Check its status:

```bash
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:

```bash
kubectl cluster-info
```

List the nodes:

```bash
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](https://kubernetes.io/docs/tutorials/hello-minikube/):

```bash
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:

```bash
kubectl get deployments
```

You should see output similar to this:

```text
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
hello-node   1/1     1            1           30s
```

Now list Pods:

```bash
kubectl get pods
```

You should see one Pod with a generated name:

```text
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:

```bash
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:

```bash
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:

```bash
kubectl get pods
```

Then read its logs:

```bash
kubectl logs <pod-name>
```

For a Deployment with one container per Pod, you can also run:

```bash
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:

```bash
kubectl expose deployment hello-node \
  --type=LoadBalancer \
  --port=8080
```

List Services:

```bash
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:

```bash
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:

```bash
kubectl scale deployment hello-node --replicas=3
```

Watch the Pods:

```bash
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:

```bash
kubectl scale deployment hello-node --replicas=1
```

Kubernetes removes the extra Pods.

## See self-healing in action

Make sure the Deployment wants one Pod:

```bash
kubectl get deployment hello-node
```

List the Pod:

```bash
kubectl get pods
```

Delete that Pod:

```bash
kubectl delete pod <pod-name>
```

Now list Pods again:

```bash
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:

```bash
kubectl delete service hello-node
kubectl delete deployment hello-node
```

Create a file named `app.yaml`:

```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:

```bash
kubectl apply -f app.yaml
```

Kubernetes reports that it created the Deployment and Service.

Check them together:

```bash
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:

```bash
kubectl port-forward service/hello-web 8080:80
```

Keep that command running and open [http://localhost:8080](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:

```yaml
replicas: 2
```

to:

```yaml
replicas: 4
```

Apply the file again:

```bash
kubectl apply -f app.yaml
```

Now check the Pods:

```bash
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`:

```yaml
image: nginx:1.27-alpine
```

Apply it:

```bash
kubectl apply -f app.yaml
```

Watch the rollout:

```bash
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:

```bash
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:

```yaml
image: nginx:1.27-alpine
```

is clearer than:

```yaml
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:

```yaml
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:

```yaml
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:

```bash
kubectl get namespaces
```

You can create one for an application or environment:

```bash
kubectl create namespace demo
```

Then list Pods inside it:

```bash
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:

1. A Pod runs the application.
2. A Service gives matching Pods a stable endpoint.
3. 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:

- `ClusterIP` is reachable inside the cluster and is the default
- `NodePort` opens a port on every node
- `LoadBalancer` asks the environment for an external load balancer
- `ExternalName` maps 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:

```yaml
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:

```bash
kubectl get deployments,pods,services
```

Then inspect the failing Pod:

```bash
kubectl describe pod <pod-name>
```

Read its logs:

```bash
kubectl logs <pod-name>
```

Check recent events:

```bash
kubectl get events --sort-by=.metadata.creationTimestamp
```

Common statuses give useful hints:

- `Pending` may mean the scheduler cannot place the Pod
- `ImagePullBackOff` means Kubernetes cannot pull the image
- `CrashLoopBackOff` means the container keeps failing and restarting
- `Running` means the process is running, not necessarily that the app is ready

If networking fails, inspect the Service:

```bash
kubectl describe service hello-web
```

Then check whether its selector matches the Pod labels:

```bash
kubectl get pods --show-labels
```

This workflow solves many beginner problems.

## Useful `kubectl` commands

List resources:

```bash
kubectl get pods
kubectl get deployments
kubectl get services
```

Get more information:

```bash
kubectl get pods -o wide
kubectl describe pod <pod-name>
```

Read logs:

```bash
kubectl logs <pod-name>
```

Follow logs:

```bash
kubectl logs -f <pod-name>
```

Apply a manifest:

```bash
kubectl apply -f app.yaml
```

Delete objects from a manifest:

```bash
kubectl delete -f app.yaml
```

Wait for a rollout:

```bash
kubectl rollout status deployment/hello-web
```

Forward a local port:

```bash
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`:

```bash
kubectl delete -f app.yaml
```

Check that they are gone:

```bash
kubectl get deployments,pods,services
```

The built-in `kubernetes` Service remains. It represents the Kubernetes API.

Stop the local cluster:

```bash
minikube stop
```

This keeps the cluster so you can start it again later.

To remove the local cluster completely, run:

```bash
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:

1. A **cluster** contains the whole system.
2. **Nodes** provide machines where workloads run.
3. A **Pod** runs one or more closely related containers.
4. A **Deployment** keeps the requested Pods running and updates them.
5. A **Service** gives matching Pods a stable network endpoint.
6. **Labels and selectors** connect those objects.
7. `kubectl` talks to the Kubernetes API.
8. 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.
