Getting Started with Kubernetes

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

This page gets a working local Kubernetes cluster and a first deployment running. It targets kubectl 1.37, kind v0.33 (node image kindest/node:v1.37.0) and minikube v1.39, against a Kubernetes v1.37 control plane; see Introduction and Architecture for what those pieces are and the section landing page for the full reading path. Everything here runs on a laptop — no cloud account is needed.

Installing kubectl

kubectl is the CLI client that talks to a cluster’s API server over HTTPS; it does not need a running cluster to be installed. There are three supported ways to get it on a developer machine:

Method Notes

Homebrew (macOS, Linux)

brew install kubectl. Convenient, but the version tracks Homebrew’s own release cadence rather than Kubernetes' own repositories.

The pkgs.k8s.io package repositories (Linux)

apt/dnf/yum packages published directly by the Kubernetes project, one repository per minor version. This is the recommended way to install and, more importantly, to pin and upgrade kubectl predictably on a workstation or CI runner.

Direct binary download

A single static binary from dl.k8s.io, for a platform without a package manager or a pinned patch version.

Homebrew

brew install kubectl
kubectl version --client

The pkgs.k8s.io repositories

The legacy Google-hosted apt.kubernetes.io/yum.kubernetes.io repositories used by older books and tutorials are gone; the project now publishes one repository per minor version under pkgs.k8s.io, so installing from the v1.37 repository can never silently pull in a v1.38 package:

# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.37/deb/ /' \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl
# Fedora/RHEL/CentOS Stream (dnf/yum)
cat <<'EOF' | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/repodata/repomd.xml.key
EOF
sudo dnf install -y kubectl

Derived from that page and the pkgs.k8s.io introduction blog post. To move to the next minor later, edit the repository URL to v1.38 and reinstall — the package manager then upgrades kubectl deliberately instead of picking it up on a routine apt upgrade.

Binary download

curl -LO "https://dl.k8s.io/release/v1.37.1/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/v1.37.1/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

macOS and Windows binaries are published under the same dl.k8s.io/release/<version>/bin/<os>/<arch>/ layout; see Install Tools.

The version-skew policy

kubectl is supported within one minor version of the API server, in either direction: a v1.37 client works against a v1.36, v1.37 or v1.38 server, but not against v1.35 or v1.39. Server-to-server components (kubelet, etcd) have tighter or different skew rules of their own. Practical consequences:

  • Pin kubectl to the newest API server version you talk to, not the oldest — an older client against a newer server risks missing a field the server expects.

  • A CI pipeline that targets several clusters (dev on v1.36, prod on v1.37) needs one kubectl binary within skew of both, or one binary per cluster.

  • kubectl version prints both the client and server (Server Version:) so a skew problem shows up immediately.

Local clusters

A local cluster removes the cost of a cloud account while learning the API. All the examples in this section run on one of the three options below; Cluster Setup and Administration covers kubeadm and real multi-node clusters, and Managed Kubernetes and Distributions covers cloud-managed options.

kind (Kubernetes in Docker)

kind runs each cluster node as a Docker (or Podman) container, using a purpose-built node image that itself runs containerd and kubelet. It starts and tears down in seconds, supports multiple nodes on one machine, and is the tool the Kubernetes project itself uses to test Kubernetes.

brew install kind    # or: go install sigs.k8s.io/kind@v0.33.0
kind create cluster --name bookshelf --image kindest/node:v1.37.0
kubectl cluster-info --context kind-bookshelf

A single-node cluster is enough for most of this section, but several later pages — the Ingress/Gateway API pages in particular — need a node that can receive traffic on host ports 80 and 443, and a cluster with more than one node to show pod anti-affinity, topology spread and DaemonSets meaningfully. The following multi-node configuration provides both, and is reused by name later in this section:

kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
- role: worker
- role: worker
kind create cluster --name bookshelf --image kindest/node:v1.37.0 --config kind-config.yaml
kubectl get nodes
kubectl get node bookshelf-control-plane -o jsonpath='{.metadata.labels.ingress-ready}'

extraPortMappings forwards the host’s ports 80/443 to the control-plane container’s ports 80/443. An ingress controller scheduled onto the node the ingress-ready=true label marks (via a nodeSelector) and bound to that node’s ports 80/443 with hostPort then becomes reachable at http://localhost from the developer’s own machine, without any cloud load balancer. A plain NodePort Service would not do: its port comes from the 30000-32767 range, not port 80. Later Gateway API pages install a gateway controller onto this same cluster and reuse kind-config.yaml unchanged.

Load a locally built image into the cluster’s containerd store without pushing it to a registry:

docker build -t bookshelf-web:dev .
kind load docker-image bookshelf-web:dev --name bookshelf

Delete the cluster with kind delete cluster --name bookshelf when done.

minikube

minikube runs a single- or multi-node cluster inside a VM or container driver (Docker, Podman, HyperKit, Hyper-V, KVM2, …). Since v1.39 the default node runtime inside the cluster is containerd rather than Docker:

brew install minikube
minikube start --kubernetes-version=v1.37.1 --driver=docker
kubectl get nodes
minikube dashboard             # opens the bundled web UI
minikube addons list           # ingress, metrics-server, registry, ...
minikube image load bookshelf-web:dev
minikube stop

Source: minikube start

minikube ships add-ons (an Ingress-NGINX controller, metrics-server, a local registry) as one-line toggles, which kind leaves to the reader to install separately; see also Hello Minikube.

Docker Desktop

Docker Desktop bundles a Kubernetes cluster that runs inside the Desktop VM next to the Docker Engine: enable it once from Settings, and kubectl picks up a docker-desktop context automatically. It offers two provisioning modes:

  • kubeadm — the original mode: always single-node, with the Kubernetes version fixed by the Docker Desktop release. It can use the Docker Engine’s image store, so an image built with docker build is visible to the cluster with no load step at all.

  • kind — available since Docker Desktop 4.38: multi-node, with a selectable Kubernetes version. Its nodes do not share the Docker Engine’s image store, so a locally built image must be pushed to a registry or loaded into the nodes, as with standalone kind.

See Getting Started with Docker for installing Docker Desktop itself.

Comparison

kind minikube Docker Desktop

Nodes

Multiple, each a container; easy to script (kind-config.yaml)

One or more, each a VM or container depending on the driver

One (kubeadm mode) or several (kind mode)

Runtime inside the cluster

containerd

containerd (default since v1.39)

containerd

Start-up time

Seconds

Tens of seconds

Already running once Kubernetes is enabled

Loading a local image

kind load docker-image

minikube image load, or eval $(minikube docker-env) with the Docker driver

Automatic in kubeadm mode (same image store as docker build); push or load in kind mode

Add-ons

None built in; install an ingress controller etc. yourself

Built-in toggles (minikube addons enable ingress, metrics-server, …)

A fixed, non-configurable set

Best for

CI, multi-node scenarios, testing against a specific kindest/node version

A full-featured single machine with add-ons and a dashboard

Developers who already run Docker Desktop and want minimal extra setup

Kubeconfig contexts

A kubeconfig file (~/.kube/config by default, or the KUBECONFIG environment variable) holds one or more clusters (an API server address and CA certificate), users (credentials) and contexts (a cluster/user/ namespace triple with a name). kind and minikube each add their own cluster, user and context automatically when a cluster starts.

kubectl config get-contexts
kubectl config use-context kind-bookshelf
kubectl config set-context --current --namespace=bookshelf
kubectl config view --minify                      # just the active context
kubectl config current-context

kubectl config use-context switches which cluster and user every subsequent command targets; set-context --current --namespace changes only the default namespace for the active context, so kubectl get pods means kubectl get pods -n bookshelf without repeating -n on every command. Both changes are stored in the kubeconfig file itself, so they persist across shells. Namespaces themselves — what they are for and when to use them — are covered in Objects and the Kubernetes API.

Derived also from kubectl config reference.

The first Bookshelf deploy

The rest of this section builds out the full Bookshelf application — catalog-api, reviews-api, web, PostgreSQL, Redis and a notifier worker — but the very first deploy only needs one piece of it, following the same imperative-command flow as the official Kubernetes Basics tutorial: create a Deployment, expose it, look at it, scale it, update it, and delete it.

kubectl create deployment web --image=ghcr.io/example/bookshelf-web:1.0.0
kubectl get deployments,pods -o wide
kubectl describe deployment web
kubectl expose deployment web --port=80 --target-port=8080 --name=web
kubectl get service web

create deployment wraps the Pod template in a Deployment (see ReplicaSets and Deployments for what that buys you over a bare Pod), and expose creates a matching ClusterIP Service selecting the Deployment’s pods (see Services). Reach the Service from the local machine with port-forward, which tunnels a local TCP port straight to a Pod without going through the cluster’s networking at all:

kubectl port-forward service/web 8080:80
# in another shell:
curl -s http://localhost:8080/ | head -n 5

Scale it, roll out a new version and watch the rollout, then roll back:

kubectl scale deployment web --replicas=3
kubectl get pods -l app=web -o wide

kubectl set image deployment/web bookshelf-web=ghcr.io/example/bookshelf-web:1.1.0
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web

kubectl create deployment names the container after the image (bookshelf-web), which is the name set image targets. set image changes the Pod template’s image, which the Deployment controller turns into a new ReplicaSet and a rolling update; rollout status blocks until the update finishes (or reports why it hasn’t), and rollout undo reverts to the previous ReplicaSet. ReplicaSets and Deployments covers the update strategy fields (maxSurge, maxUnavailable) and the rest of kubectl rollout behind these commands.

Clean up:

kubectl delete service web
kubectl delete deployment web

(in particular the "Using kubectl to Create a Deployment", "Viewing Your Application", "Using kubectl to Expose an App", "Scale an Application" and "Update an Application" modules); see also Hello Minikube, which walks the same flow specifically against minikube.

Shell completion

kubectl ships a completion script generator so the shell can tab-complete subcommands, flags, resource types and even resource names fetched live from the cluster:

# bash (needs bash-completion installed)
echo 'source <(kubectl completion bash)' >> ~/.bashrc

# zsh
echo 'source <(kubectl completion zsh)' >> ~/.zshrc

# a short alias also gets completion, once registered:
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc   # bash
echo 'alias k=kubectl' >> ~/.zshrc
echo 'compdef k=kubectl' >> ~/.zshrc                            # zsh

Derived from that page and kubectl completion --help.

The local dev loop

Putting the pieces above together, a typical inner-loop iteration on a kind cluster looks like this: change code, rebuild the image, load it into the cluster (no registry needed), re-apply the manifest so the new image is picked up, and re-open the port-forward to try it.

flowchart LR a["Edit source code"] --> b["docker build -t bookshelf-web:dev ."] b --> c["kind load docker-image bookshelf-web:dev --name bookshelf"] c --> d["kubectl apply -f web-deployment.yaml"] d --> e["kubectl rollout status deployment/web"] e --> f["kubectl port-forward service/web 8080:80"] f --> g["curl / browser against localhost:8080"] g -.->|repeat| a

kubectl apply (rather than set image) is the declarative form used from here on, once a manifest exists on disk; kubectl Essentials contrasts the imperative commands used above with apply in depth. imagePullPolicy: IfNotPresent (or Never) on the Deployment’s container keeps kubectl apply from trying to pull bookshelf-web:dev from a registry that does not have it, now that kind load docker-image has already placed it on every node.

Where to go next

Read kubectl Essentials for the rest of the kubectl surface used throughout this section, then Objects and the Kubernetes API for the object model and namespaces this page only introduced in passing. The full reading order is on the section landing page.