Hello Kubernetes⚓︎
This tutorial builds on Hello Docker to run a container image in kubernetes.
Create Cluster with Kind⚓︎
Since you probably don't have a spare kubernetes cluster laying around, we'll use kind to create a kubernetes cluster which runs in docker.
Install Kind⚓︎
From Installing From Release Binaries in the kind documentation.
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.30.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
Create Cluster⚓︎
Create a kind cluster named tutorial with kubernetes version 1.35.1.
kind create cluster --name tutorial --image kindest/node:v1.35.1
Connect to Cluster⚓︎
A kubernetes cluster isn't very useful if we don't have a way to interact with it.
The primary tool we'll use is a command line interface called kubectl.
Install Kubectl⚓︎
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/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.34/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt install kubectl
Get Cluster Version⚓︎
Tip
If you have multiple clusters, you may want to run this command to make the kind-tutorial context active.
kubectl config use-context kind-tutorial
kubectl version
Client Version: v1.34.2
Kustomize Version: v5.7.1
Server Version: v1.34.0
Create a Pod⚓︎
Next, we'll create a pod which runs a container, much like we did for Run the Container in docker.
Create Pod Definition⚓︎
If you're using Visual Studio Code, the Kubernetes Extension will provide completion and tooltips when creating resources.
Create a new file hello-node-pod.yaml and paste the following.
apiVersion: v1
kind: Pod
metadata:
name: hello-node-pod #(1)!
spec:
containers: #(2)!
- name: app
# Image we built in the previous tutorial (3)
image: hello-node:1.0.1
imagePullPolicy: Never #(4)!
ports:
# This port number should match index.js (5)
- containerPort: 3000
namecan be anything that contains only letters, numbers, and dashes. In this tutorial, it's only used inkubectlcommands.- A
podcan have more than one container, but we only need one in this tutorial. - See Hello Docker
- Setting
imagePullPolicy: Neveris an unusual thing to do. We're only using it because we built our app's image locally. We know the image doesn't exist in a public registry, so we don't want to try to pull it from Docker Hub. - See the line
const port = 3000;from Hello World Wide Web - Add Source
Apply Pod Definition⚓︎
Apply the pod definition to create the pod in the cluster.
kubectl apply -f hello-node-pod.yaml
pod/hello-node-pod created
Get Pods⚓︎
Show all pods to see the one we just created.
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-node-pod 0/1 ErrImageNeverPull 0 12s
Note the 0/1 READY status which indicates the pod's container is not starting.
View Pod Events⚓︎
Let's take a look at the pod events to see why it's not READY.
kubectl events --for pod/hello-node-pod
LAST SEEN TYPE REASON OBJECT MESSAGE
71s Normal Scheduled Pod/hello-node-pod Successfully assigned default/hello-node-pod to tutorial-control-plane
10s (x7 over 70s) Warning ErrImageNeverPull Pod/hello-node-pod Container image "hello-node:1.0.1" is not present with pull policy of Never
10s (x7 over 70s) Warning Failed Pod/hello-node-pod Error: ErrImageNeverPull
The image hello-node:1.0.1 is not available in the cluster.
Load Image⚓︎
Run a kind command to load the docker image into the kubernetes cluster registry.
kind load docker-image --name tutorial hello-node:1.0.1
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-node-pod 1/1 Running 0 3m15s
Show Logs⚓︎
Similar to docker logs, we can use kubectl to show the pod logs.
kubectl logs hello-node-pod
Server running at http://0.0.0.0:3000/
Port Forwarding⚓︎
We'd like to access the app from a browser.
We'll learn other ways to do this later, but for now we can forward a port on our host to the container.
To make sure we're communicating with our app running in kubernetes instead of the previous tutorial version, we'll forward a different host port (3002) to container port 3000.
kubectl port-forward pod/hello-node-pod 3002:3000
Forwarding from 127.0.0.1:3002 -> 3000
Forwarding from [::1]:3002 -> 3000
kubectl port-forward will keep running to handle connections.
Open http://127.0.0.1:3002/ in the browser, and we can again see our Hello World greeting!

kubectl port-forward will also generate some additional output.
Handling connection for 3002
Now that we're done testing, press Ctrl+C in your terminal to stop the kubectl port-forward.
Cleanup⚓︎
Let's clean up the cluster that we created so the container doesn't consume resources.
Delete Pod⚓︎
If we're deleting the whole cluster anyways, it's not really necessary to delete the pod first. However, it's good to know how to delete it just in case.
kubectl delete -f hello-node-pod.yaml
Verify that the pod is gone.
kubectl get pod
This command is going to hang for awhile.
Remember our old nemesis from Stopping the Container?
We've got the same problem here, but kubernetes has different default behavior than docker.
It will send SIGTERM to ask the container's main process, wait 30 seconds for it to exit, and then send SIGKILL which can't be ignored.
No resources found in default namespace.
Delete Cluster⚓︎
While an idle kubernetes node doesn't consume many resources, it still requires some that we can free up by deleting the cluster. It's easy to recreate if you want to try more experiments.
kind delete cluster --name tutorial
Deleting cluster "tutorial" ...
Deleted nodes: ["tutorial-control-plane"]
Review⚓︎
We were able to start our app in kubernetes, just like we did last time using docker.
However, docker port-forward isn't a very friendly way to access the app.
Next time, we'll see more advanced ways to deploy apps and route network traffic in kubernetes.