Kubernetes for Beginners: Deploy Your First App (Step-by-Step)
Kubernetes feels overwhelming until you understand its four fundamental building blocks. Once you grasp what a Pod, Deployment, Service, and Ingress actually do - and why they are separate abstractions - the rest of Kubernetes starts to make sense. This guide skips the theory and goes straight to what you need to ship software.
Why Kubernetes Exists
When you run a container in production, several questions immediately arise: what happens when it crashes? How do you run five copies of it for load balancing? How do you update it without downtime? How do external users reach it? How do you manage secrets across dozens of services?
Kubernetes answers all of these questions through a consistent declarative API: you describe the desired state of your system in YAML, apply it to the cluster, and Kubernetes continuously works to make reality match that description. A crashed container gets restarted automatically. A node dies and its workloads are rescheduled elsewhere. You update a deployment and Kubernetes rolls it out gradually, ready to roll back if something goes wrong.
The Mental Model: Everything Is an Object
In Kubernetes, every resource is an object described in YAML with four top-level fields: apiVersion, kind, metadata, and spec. You apply objects to a cluster, and the control plane reconciles them. That's the whole model. Everything else is just knowing what kinds of objects exist and what their spec fields mean.
apiVersion: apps/v1 # API group and version
kind: Deployment # The type of object
metadata:
name: my-api # Unique name in this namespace
namespace: production
spec: # The desired state
Pods: The Smallest Deployable Unit
A Pod is the smallest thing Kubernetes can schedule. It contains one or more containers that share the same network namespace (same IP, same ports) and can share volumes. In practice, most Pods contain a single container. Sidecar patterns (logging agents, proxies) use multi-container Pods.
apiVersion: v1
kind: Pod
metadata:
name: my-api-pod
spec:
containers:
- name: api
image: myorg/api:v1.2.3
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
You almost never create Pods directly. They are managed by higher-level controllers like Deployments. If you create a bare Pod and it crashes, it stays crashed - there is no restart logic at the Pod level. That is the Deployment's job.
Deployments: Managed, Replicated Pods
A Deployment manages a set of identical Pods. You tell it what container to run, how many replicas you want, and what update strategy to use. Kubernetes handles the rest: scheduling Pods across nodes, restarting crashed Pods, and rolling out new versions.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: my-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Allow 1 extra Pod during update
maxUnavailable: 0 # Never take a Pod down before a new one is ready
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: api
image: myorg/api:v1.2.3
ports:
- containerPort: 8080
env:
- name: NODE_ENV
value: production
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
The selector.matchLabels and template.metadata.labels must match. This is how the Deployment knows which Pods it owns. Always use livenessProbe (restart on failure) and readinessProbe (only send traffic when ready). Without readiness probes, a rolling update will send traffic to Pods that are still starting up.
Services: Stable Networking for Pods
Pods are ephemeral. Their IPs change every time they restart or get rescheduled. A Service provides a stable IP and DNS name that always routes to the correct set of Pods (selected by label). There are four Service types:
- ClusterIP - Internal only. Accessible within the cluster. The default.
- NodePort - Exposes the Service on each node's IP at a static port. Reachable from outside the cluster but awkward for production.
- LoadBalancer - Provisions a cloud load balancer (AWS ALB, GCP LB, etc.). The standard way to expose production services.
- ExternalName - Maps a Service to a DNS name. Used to abstract external dependencies.
apiVersion: v1
kind: Service
metadata:
name: my-api-svc
namespace: production
spec:
type: ClusterIP # Internal only
selector:
app: my-api # Routes to Pods with this label
ports:
- port: 80 # Service port (what callers use)
targetPort: 8080 # Container port (what Pods listen on)
protocol: TCP
Once this Service exists, any Pod in the cluster can reach your API at http://my-api-svc (within the same namespace) or http://my-api-svc.production.svc.cluster.local (across namespaces).
Ingress: HTTP Routing Into the Cluster
An Ingress routes external HTTP/HTTPS traffic to internal Services based on host name or URL path. It requires an Ingress controller (Nginx Ingress, AWS ALB Ingress Controller, Traefik) to be installed in the cluster.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts:
- api.myapp.com
secretName: api-tls-cert
rules:
- host: api.myapp.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: my-api-svc
port:
number: 80
- path: /admin
pathType: Prefix
backend:
service:
name: admin-svc
port:
number: 80
Scan Your Site for Free
Our Exposure Checker runs 19 parallel security checks - SSL, headers, exposed paths, DNS, open ports, and more.
Run Free Security ScanNamespaces: Logical Isolation
Namespaces divide a single cluster into virtual sub-clusters. They scope resource names and are used for environment separation (production, staging), team separation, or access control. Resource quotas and RBAC policies apply per namespace.
# Create a namespace
kubectl create namespace staging
# Apply a manifest to a specific namespace
kubectl apply -f deployment.yaml -n staging
# Set a default namespace for your session
kubectl config set-context --current --namespace=production
# List all namespaces
kubectl get namespaces
Essential kubectl Commands
You will use these every day:
# Apply or update resources from YAML
kubectl apply -f deployment.yaml
# Get resources
kubectl get pods # List pods in current namespace
kubectl get pods -n production # List pods in specific namespace
kubectl get pods -A # All namespaces
kubectl get pods -o wide # Include node, IP info
kubectl get all # Pods, services, deployments
# Describe a resource (events, conditions, full config)
kubectl describe pod my-api-pod-xxx
kubectl describe deployment my-api
# Logs
kubectl logs my-api-pod-xxx
kubectl logs my-api-pod-xxx -f # Follow (tail)
kubectl logs my-api-pod-xxx --previous # Logs from crashed previous container
# Execute a command in a running container
kubectl exec -it my-api-pod-xxx -- sh
kubectl exec -it my-api-pod-xxx -- curl localhost:8080/health
# Scale a deployment
kubectl scale deployment my-api --replicas=5
# Rolling restart (picks up new ConfigMap/Secret values)
kubectl rollout restart deployment/my-api
# Check rollout status
kubectl rollout status deployment/my-api
# Rollback to previous version
kubectl rollout undo deployment/my-api
# Delete resources
kubectl delete pod my-api-pod-xxx
kubectl delete -f deployment.yaml
Resource Requests and Limits: Why They Matter
Every container should declare CPU and memory requests (minimum guaranteed) and limits (maximum allowed). Without requests, the scheduler cannot place your Pod intelligently and nodes get overloaded. Without limits, a runaway process can starve other containers.
resources:
requests:
memory: "128Mi" # Kubernetes guarantees this much
cpu: "100m" # 100 millicores = 0.1 CPU cores
limits:
memory: "256Mi" # Container is OOM-killed if it exceeds this
cpu: "500m" # CPU is throttled (not killed) at this cap
Frequently Asked Questions
What is the difference between a Pod and a container?
A container is a Linux process in an isolated namespace, managed by a container runtime (containerd, Docker). A Pod is a Kubernetes abstraction that wraps one or more containers, giving them a shared network identity (IP address) and optionally shared storage. Kubernetes schedules and manages Pods, not individual containers.
Why does Kubernetes use labels and selectors instead of names?
Names are unique per namespace - you can only have one resource with a given name. Labels are arbitrary key-value pairs that can be shared across many resources. A Service selects all Pods with app: my-api regardless of how many there are or what their names are. This decouples the Service from specific Pod instances, which is essential for dynamic scaling.
What happens when a Pod crashes?
If the Pod is managed by a Deployment, Kubernetes restarts it automatically. The restart count is tracked and visible in kubectl get pods. If a Pod restarts too quickly too many times, Kubernetes applies exponential backoff (CrashLoopBackOff state) to avoid a restart storm. The Deployment's desired replica count is always maintained.
How do I expose my application to the internet?
The standard production approach is: Deployment → ClusterIP Service → Ingress. The Deployment runs your containers, the Service gives them a stable internal address, and the Ingress routes external traffic to the Service based on host/path rules. The Ingress controller handles TLS termination and load balancing.
What is a ReplicaSet and how is it different from a Deployment?
A ReplicaSet ensures that a specified number of identical Pods are running at all times. A Deployment manages ReplicaSets and adds rolling update and rollback capabilities on top. When you update a Deployment's container image, it creates a new ReplicaSet and gradually shifts traffic from the old one to the new one. You almost never interact with ReplicaSets directly.
Next Steps
With Pods, Deployments, Services, and Ingress understood, the next topics to learn are ConfigMaps and Secrets for managing configuration, Horizontal Pod Autoscaling for automatic scaling, PersistentVolumeClaims for storage, and RBAC for access control.
Use our free YAML Validator to catch syntax errors in your Kubernetes manifests before applying them to your cluster. Explore our 70+ free developer tools for more DevOps utilities.
Use our free tool here → YAML Validator
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.