Agentic AI with Java: Live Cohort
KubernetesPods & Services

First Pod Creation in Kubernetes

Write your first Pod manifest, fix the real YAML errors that come up, inspect the pod with get, -o wide and describe, compare kubectl create with apply, and delete it.

Time to run something. We will write a manifest for a single pod that runs a Spring Boot application, submit it to our EKS cluster, look at it from a few angles, and then delete it. Along the way we will hit two YAML mistakes that almost everyone makes in their first week, so you will recognise them when they happen to you.

Make sure your cluster from Creating Your First EKS Cluster is running and kubectl get nodes shows two Ready nodes.

The image we will run

The lecture runs a Spring Boot web application image that listens on port 8080, pulled from Docker Hub. Use the Spring Boot image you pushed in the Docker course (Push Spring Boot Image to Docker Hub), written as <your-dockerhub-username>/springboot-app:latest below.

No image of your own yet?

Any public image works for this lesson. nginx:1.29 is a good choice. It listens on port 80, so change containerPort: 8080 to containerPort: 80 wherever you use it. Everything else stays the same.

Step 1: Write the manifest

On the EKS home server, create the file:

vi k8s-pod.yml

Press i and type the following:

k8s-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
  labels:
    app: telusko-webapp
spec:
  containers:
    - name: webapp-container
      image: <your-dockerhub-username>/springboot-app:latest
      ports:
        - containerPort: 8080

Save with Esc, then :wq.

LineMeaning
apiVersion: v1Pods belong to the core API group, version v1. Note the capital V in the key name apiVersion
kind: PodCapital P. pod is rejected
metadata.name: webappThe pod's name. kubectl commands use it
metadata.labels.app: telusko-webappA key-value label. Services will use it later to find this pod
spec.containersA list of containers in the pod. Here, just one
name: webapp-containerName of the container inside the pod
imageThe image the container runtime pulls from the registry
ports.containerPort: 8080The port the application listens on inside the container

What containerPort does (and does not do)

containerPort is mostly documentation. It tells readers and tools which port the app listens on, and it lets you give the port a name. It does not open the application to the outside world, and leaving it out does not block the port either. Without a Service, the pod is reachable only from inside the cluster, using the pod's IP. We fix that with Services starting in the next lesson.

Step 2: Apply it, and fix the errors

kubectl apply -f k8s-pod.yml

In the lecture, the first attempt failed:

Error from server (BadRequest): error when creating "k8s-pod.yml": Pod in version "v1" cannot be
handled as a Pod: json: cannot unmarshal object into Go struct field Container.spec.containers.ports
of type []v1.ContainerPort

Cause: the line was written -containerPort: 8080, without a space after the dash. YAML then reads ports as a map instead of a list, and Kubernetes expects a list of ports. The fix is - containerPort: 8080.

After fixing that, the second attempt failed differently:

Error from server (BadRequest): error when creating "k8s-pod.yml": Pod in version "v1" cannot be
handled as a Pod: strict decoding error: unknown field "metadata.app"

Cause: app: telusko-webapp was at the same indentation as labels:, so YAML treated app as a field of metadata, and metadata has no field called app. Indenting it two more spaces makes it a child of labels.

Error text containsUsual cause
cannot unmarshal object into ... of type []...Something that should be a list is not. Check the -
cannot unmarshal array into ...The opposite: a list where a single map is expected
strict decoding error: unknown field "X.Y"Wrong indentation or a typo in a field name
no matches for kind "pod" in version "v1"Wrong case in kind, or wrong apiVersion
error converting YAML to JSONTabs instead of spaces, or a missing space after :

Third time lucky:

pod/webapp created

Step 3: Look at the pod

kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
webapp   1/1     Running   0          17s
ColumnMeaning
READY 1/11 of 1 containers in the pod is ready
STATUS RunningThe pod is on a node and its container is running. Other statuses are covered in Kubernetes Pod Statuses
RESTARTS 0How many times the container restarted. A rising number means it keeps crashing
AGETime since the pod was created

If you are quick enough you may see ContainerCreating first, while the image is pulled.

For more detail, add -o wide:

kubectl get pods -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP              NODE                                         NOMINATED NODE   READINESS GATES
webapp   1/1     Running   0          46s   192.168.23.99   ip-192-168-9-81.ap-south-1.compute.internal  <none>           <none>

Now you can see the pod's IP and the node the scheduler chose. On EKS the pod IP comes from your VPC subnet, thanks to the VPC CNI add-on.

When something goes wrong, describe is your best friend:

kubectl describe pod webapp

It prints the labels, the node, the IP, each container's image, image ID, port, state and restart count, and at the bottom the Events list: Scheduled, Pulling, Pulled, Created, Started. When a pod is stuck, the reason is almost always in those events.

A few more useful commands:

kubectl logs webapp                         # the application's stdout, here the Spring Boot startup log
kubectl get pod webapp -o yaml              # the full object, including the status section
kubectl port-forward pod/webapp 8080:8080   # temporary tunnel for testing, see note below

port-forward opens a tunnel from port 8080 on the EKS home server to the pod. In a second SSH session, curl localhost:8080 reaches the app. It is a debugging tool only; real access goes through a Service.

Step 4: create vs apply

Both commands submit a manifest. The difference shows when the object already exists.

kubectl create -f k8s-pod.yml
Error from server (AlreadyExists): error when creating "k8s-pod.yml": pods "webapp" already exists
kubectl apply -f k8s-pod.yml
pod/webapp unchanged
kubectl create -fkubectl apply -f
Object does not existCreates itCreates it
Object existsError: AlreadyExistsCompares and updates only what changed, or reports unchanged
StyleImperative: "create this now"Declarative: "make it look like this file"
Typical useOne-off creationEveryday work, CI/CD pipelines, GitOps

Teams use apply almost always, because the same command works the first time and every time after.

You cannot change most fields of a running pod

Try changing the image in the file and running kubectl apply again: it works, because the image is one of the few pod fields that can be updated in place. Now try changing containerPort or adding a new container: you get Forbidden: pod updates may not change fields other than .... Pods are meant to be replaced, not edited. Deployments, which you meet soon, handle that replacement for you.

Step 5: Delete the pod

kubectl delete pod webapp
# or, using the file
kubectl delete -f k8s-pod.yml
pod "webapp" deleted
kubectl get pods
No resources found in default namespace.

The pod is gone and nothing brings it back. A bare pod has no controller watching it. If it had crashed or its node had died, it would also have stayed gone. That limitation is exactly why we will move to ReplicaSets and Deployments later.

The word "default namespace" in that message refers to Namespaces, which we cover in a later section.

Try It Yourself

  1. Create the webapp pod and watch it start with kubectl get pods -w (press Ctrl+C to stop watching).
  2. Introduce each YAML mistake from this lesson on purpose and match the error you get to the table above.
  3. Run kubectl describe pod webapp and write down the five events at the bottom in order.
  4. Change the image to a tag that does not exist (for example nginx:does-not-exist), apply, and look at the STATUS column and the events. You will see ErrImagePull and then ImagePullBackOff.
  5. Delete the pod and confirm nothing recreates it.

Cluster cost

If you are done for the day, delete the cluster: eksctl delete cluster --name telusko-learning --region ap-south-1 --wait.

Official references


Written By: Shramik Masti

How is this guide?

Last updated on