Skip to main content

Your First Deployment: Hello World

Key Takeaways for AI & Readers
  • Deployment Object: Creating a Deployment triggers a chain reaction: Deployment → ReplicaSet → Pod. The Deployment manages rollouts, the ReplicaSet maintains replica count, and the Pod runs the container.
  • Service Exposure: Pods are isolated by default — they have cluster-internal IPs only. A Service (e.g., NodePort or LoadBalancer) is required to expose them to external traffic.
  • Declarative vs. Imperative: kubectl create is imperative (do this now). kubectl apply -f is declarative (make it look like this). Production workflows use declarative YAML files.
  • Observability: Commands like get, describe, logs, and port-forward are essential for verifying application state and debugging issues.

Enough theory — let's deploy an application. This page walks you through the standard workflow for launching a containerized app on Kubernetes, verifying it, and understanding what happens behind the scenes at each step.


Prerequisites

Make sure you have:

  1. A running Kubernetes cluster (local setup guide)
  2. kubectl installed and configured
  3. Verify connectivity:
    kubectl cluster-info
    kubectl get nodes

1. Interactive Terminal Simulation

Follow the steps below to see the standard workflow. The visualizer simulates the terminal experience.

bash — 80x24
$ kubectl get pods
No resources found in default namespace.
_

2. Step-by-Step: The Imperative Way

The fastest way to get started is using imperative kubectl commands. This is great for learning but not recommended for production.

Create a Deployment

kubectl create deployment hello-k8s --image=nginx:alpine --replicas=2

This single command creates three Kubernetes objects:

Deployment (hello-k8s)
└── ReplicaSet (hello-k8s-6d4f8b5c7)
├── Pod (hello-k8s-6d4f8b5c7-abc12)
└── Pod (hello-k8s-6d4f8b5c7-def34)

Verify the Resources

# See all resources created
kubectl get all -l app=hello-k8s

# Check Pod status (should be "Running")
kubectl get pods -l app=hello-k8s -o wide

# Example output:
# NAME READY STATUS RESTARTS AGE IP NODE
# hello-k8s-6d4f8b5c7-abc12 1/1 Running 0 30s 10.244.1.3 worker-1
# hello-k8s-6d4f8b5c7-def34 1/1 Running 0 30s 10.244.2.5 worker-2

Inspect a Pod

# Detailed information including events, conditions, and container status
kubectl describe pod -l app=hello-k8s

# Application logs
kubectl logs -l app=hello-k8s

# Follow logs in real-time
kubectl logs -l app=hello-k8s -f

Expose the Deployment as a Service

By default, Pods only have cluster-internal IP addresses. To access your application from outside the cluster, create a Service:

kubectl expose deployment hello-k8s --port=80 --type=NodePort
# Check the Service
kubectl get service hello-k8s

# Example output:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# hello-k8s NodePort 10.96.45.123 <none> 80:31234/TCP 5s

The Service is now accessible on every node's IP at port 31234 (the NodePort).

Access the Application

# Using port-forward (works with any cluster type)
kubectl port-forward service/hello-k8s 8080:80
# Then visit http://localhost:8080 in your browser

# On Minikube
minikube service hello-k8s

# On Kind (if you configured port mappings)
curl http://localhost:<nodePort>

In production, you define your desired state in YAML files and apply them. This approach is version-controllable, reviewable, and repeatable.

Deployment YAML

# hello-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-k8s
labels:
app: hello-k8s
spec:
replicas: 2
selector:
matchLabels:
app: hello-k8s
template:
metadata:
labels:
app: hello-k8s
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10

Service YAML

# hello-service.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-k8s
spec:
selector:
app: hello-k8s
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort

Apply Both Files

kubectl apply -f hello-deployment.yaml
kubectl apply -f hello-service.yaml

# Or apply all YAML files in a directory
kubectl apply -f ./manifests/

Why Declarative Is Better

AspectImperative (kubectl create)Declarative (kubectl apply -f)
ReproducibilityMust remember the exact commandYAML file is the single source of truth
Version controlCannot be stored in GitCommit YAML to Git for history and review
Collaboration"What command did you run?""Read the YAML in the PR"
IdempotencyRunning twice creates duplicates or errorsApplying twice changes nothing
ComplexityHard to express probes, resources, volumesYAML captures the full specification

4. What Just Happened? (Under the Hood)

Let's trace the full chain of events:

kubectl create deployment / kubectl apply

  1. kubectl sends an HTTP request to the API Server
  2. The API Server validates the request, checks RBAC permissions, and runs admission webhooks
  3. The Deployment object is written to etcd

The Controller Chain Reaction

  1. The Deployment Controller detects the new Deployment and creates a ReplicaSet
  2. The ReplicaSet Controller detects the new ReplicaSet and creates Pods to match .spec.replicas
  3. The Scheduler detects unscheduled Pods and assigns each to a Node based on resource availability

On the Worker Node

  1. The kubelet on each assigned node pulls the container image from the registry
  2. The kubelet instructs containerd to create and start the container
  3. The kubelet runs the readiness probe — when it passes, the Pod is marked as Ready

The Service Connection

  1. The EndpointSlice Controller detects the Ready Pods match the Service selector
  2. It adds the Pod IPs to the Service's EndpointSlice
  3. kube-proxy on each node updates iptables/IPVS rules so traffic to the Service ClusterIP is routed to the backend Pods

5. Essential Verification Commands

CommandPurpose
kubectl get allSee all resources in the current namespace
kubectl get pods -o widePod status with node placement and IP
kubectl describe pod <name>Detailed status, conditions, events, and config
kubectl logs <pod-name>Container stdout/stderr output
kubectl logs <pod-name> -c <container>Logs from a specific container in a multi-container Pod
kubectl logs <pod-name> --previousLogs from the previous (crashed) container instance
kubectl exec -it <pod-name> -- /bin/shOpen a shell inside the container for debugging
kubectl port-forward pod/<name> 8080:80Forward local port to a Pod's port
kubectl port-forward svc/<name> 8080:80Forward local port through a Service
kubectl get events --sort-by=.lastTimestampRecent cluster events (useful for debugging)

6. Experiment: Self-Healing in Action

Try deleting a Pod and watch Kubernetes recreate it:

# Watch Pods in real-time
kubectl get pods -w &

# Delete one of the Pods
kubectl delete pod -l app=hello-k8s --field-selector=metadata.name=$(kubectl get pods -l app=hello-k8s -o jsonpath='{.items[0].metadata.name}')

# Output:
# hello-k8s-6d4f8b5c7-abc12 1/1 Terminating 0 5m
# hello-k8s-6d4f8b5c7-xyz99 0/1 Pending 0 0s
# hello-k8s-6d4f8b5c7-xyz99 1/1 Running 0 3s

The ReplicaSet detected that the actual count (1) didn't match the desired count (2) and immediately created a replacement Pod. This is the reconciliation loop in action.


7. Scaling

# Scale to 5 replicas
kubectl scale deployment hello-k8s --replicas=5

# Verify
kubectl get pods -l app=hello-k8s
# You should see 5 Running pods

# Scale back down
kubectl scale deployment hello-k8s --replicas=2

8. Cleanup

# Delete all resources we created
kubectl delete deployment hello-k8s
kubectl delete service hello-k8s

# Or if using YAML files
kubectl delete -f hello-deployment.yaml -f hello-service.yaml

Common Pitfalls

  1. ImagePullBackOff: The image name or tag is wrong, or the registry requires authentication. Check with kubectl describe pod and look at the Events section.

  2. CrashLoopBackOff: The container starts and immediately crashes. Check logs with kubectl logs <pod> --previous.

  3. Pods stuck in Pending: No node has sufficient CPU or memory. Check with kubectl describe pod for scheduling errors.

  4. Service not reachable: The Service selector labels don't match the Pod labels. Verify with:

    kubectl get endpoints hello-k8s
    # If the ENDPOINTS column is empty, the selector is wrong

What's Next?

Now that you've deployed your first application, dive deeper:

  • Pods — Understand the lifecycle, init containers, and multi-container patterns
  • Deployments — Learn rolling updates, rollbacks, and deployment strategies
  • Services — Master networking, service types, and DNS discovery