Your First Deployment: Hello World
- 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 createis imperative (do this now).kubectl apply -fis declarative (make it look like this). Production workflows use declarative YAML files. - Observability: Commands like
get,describe,logs, andport-forwardare 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:
- A running Kubernetes cluster (local setup guide)
kubectlinstalled and configured- 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.
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>
3. Step-by-Step: The Declarative Way (Recommended)
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
| Aspect | Imperative (kubectl create) | Declarative (kubectl apply -f) |
|---|---|---|
| Reproducibility | Must remember the exact command | YAML file is the single source of truth |
| Version control | Cannot be stored in Git | Commit YAML to Git for history and review |
| Collaboration | "What command did you run?" | "Read the YAML in the PR" |
| Idempotency | Running twice creates duplicates or errors | Applying twice changes nothing |
| Complexity | Hard to express probes, resources, volumes | YAML captures the full specification |
4. What Just Happened? (Under the Hood)
Let's trace the full chain of events:
kubectl create deployment / kubectl apply
kubectlsends an HTTP request to the API Server- The API Server validates the request, checks RBAC permissions, and runs admission webhooks
- The Deployment object is written to etcd
The Controller Chain Reaction
- The Deployment Controller detects the new Deployment and creates a ReplicaSet
- The ReplicaSet Controller detects the new ReplicaSet and creates Pods to match
.spec.replicas - The Scheduler detects unscheduled Pods and assigns each to a Node based on resource availability
On the Worker Node
- The kubelet on each assigned node pulls the container image from the registry
- The kubelet instructs containerd to create and start the container
- The kubelet runs the readiness probe — when it passes, the Pod is marked as
Ready
The Service Connection
- The EndpointSlice Controller detects the Ready Pods match the Service selector
- It adds the Pod IPs to the Service's EndpointSlice
- 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
| Command | Purpose |
|---|---|
kubectl get all | See all resources in the current namespace |
kubectl get pods -o wide | Pod 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> --previous | Logs from the previous (crashed) container instance |
kubectl exec -it <pod-name> -- /bin/sh | Open a shell inside the container for debugging |
kubectl port-forward pod/<name> 8080:80 | Forward local port to a Pod's port |
kubectl port-forward svc/<name> 8080:80 | Forward local port through a Service |
kubectl get events --sort-by=.lastTimestamp | Recent 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
-
ImagePullBackOff: The image name or tag is wrong, or the registry requires authentication. Check with
kubectl describe podand look at the Events section. -
CrashLoopBackOff: The container starts and immediately crashes. Check logs with
kubectl logs <pod> --previous. -
Pods stuck in Pending: No node has sufficient CPU or memory. Check with
kubectl describe podfor scheduling errors. -
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