Deploying Containers with kubectl
kubectl is the primary command-line interface for managing workloads on any Kubernetes cluster, including AKS. This page covers the commands you will use every day — deploying applications, inspecting state, debugging failures, and managing rollouts.
Connecting to AKS and basic commands
Before running kubectl against an AKS cluster, merge the cluster credentials into your local kubeconfig:
az aks get-credentials \
--resource-group myResourceGroup \
--name myAKSCluster
# Verify the connection
kubectl cluster-info
kubectl get nodesThe most frequently used kubectl commands fall into a handful of categories. get lists resources, describe shows detailed state and events, apply creates or updates resources from a file, delete removes resources, logs streams container output, and exec runs a command inside a running container.
# List pods in all namespaces
kubectl get pods --all-namespaces
# List pods in a specific namespace with extra columns
kubectl get pods -n production -o wide
# Describe a specific pod (shows events and state transitions)
kubectl describe pod myapp-7d4b8c9f6-xkj2p -n production
# Show all recent events in a namespace (useful for debugging)
kubectl get events -n production --sort-by='.lastTimestamp'Writing a Deployment manifest
A Deployment is the standard way to run stateless workloads on Kubernetes. It manages a ReplicaSet that keeps the desired number of pod replicas running. If a pod crashes, the Deployment controller creates a replacement. When you update the container image, it performs a rolling update.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
labels:
app: api-server
version: "2.1.0"
spec:
replicas: 3
selector:
matchLabels:
app: api-server
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: myregistry.azurecr.io/api-server:2.1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 15
env:
- name: APP_ENV
value: production
- name: PORT
value: "8080"Key fields to understand: replicas is the desired pod count. selector.matchLabels must match template.metadata.labels — this is how the Deployment knows which pods it owns. strategy.rollingUpdate with maxUnavailable: 0 ensures zero downtime during updates by only terminating old pods after new ones pass their readiness probe.
Resource requests and limits are critical in AKS. Without requests, the scheduler cannot make good placement decisions. Without limits, a misbehaving container can consume all node resources and starve its neighbors.
Complete deploy and update workflow
The typical workflow for deploying and updating an application with kubectl:
# 1. Apply the manifest (creates or updates the Deployment)
kubectl apply -f deployment.yaml
# 2. Watch the rollout progress
kubectl rollout status deployment/api-server -n production
# 3. Confirm pods are running
kubectl get pods -n production -l app=api-server
# 4. Check pod details if something looks wrong
kubectl describe pod <pod-name> -n productionTo update the container image — for example after pushing a new build:
# Update the image directly (also recorded in rollout history)
kubectl set image deployment/api-server \
api=myregistry.azurecr.io/api-server:2.2.0 \
-n production
# Or update the YAML file and re-apply
kubectl apply -f deployment.yaml
# Watch the rolling update
kubectl rollout status deployment/api-server -n productionIf the new version is broken, roll back to the previous revision immediately:
# Roll back to the previous version
kubectl rollout undo deployment/api-server -n production
# Roll back to a specific revision number
kubectl rollout undo deployment/api-server \
--to-revision=3 \
-n production
# Check rollout history
kubectl rollout history deployment/api-server -n productionViewing logs and running commands in containers
kubectl logs streams the stdout and stderr of a container. By default it returns current logs; add -f to follow live output.
# Logs from a specific pod
kubectl logs api-server-7d4b8c9f6-xkj2p -n production
# Follow live logs
kubectl logs -f api-server-7d4b8c9f6-xkj2p -n production
# Logs from all pods matching a label selector
kubectl logs -l app=api-server -n production --tail=100
# Logs from a previous container instance (after a crash)
kubectl logs api-server-7d4b8c9f6-xkj2p -n production --previous
# If a pod has multiple containers, specify which one
kubectl logs api-server-7d4b8c9f6-xkj2p \
-c sidecar-container \
-n productionkubectl exec opens a shell or runs a command inside a running container. This is invaluable for diagnosing networking issues, checking file contents, or running database migrations manually:
# Open an interactive shell in a pod
kubectl exec -it api-server-7d4b8c9f6-xkj2p \
-n production \
-- /bin/sh
# Run a single command without opening a shell
kubectl exec api-server-7d4b8c9f6-xkj2p \
-n production \
-- env | grep APP_Port-forwarding for local testing
Port-forwarding creates a tunnel between your local machine and a pod or service inside the cluster. This is useful for testing services that are not externally exposed, or for accessing the Kubernetes dashboard or monitoring UIs without creating public endpoints.
# Forward local port 8080 to port 8080 on the pod
kubectl port-forward \
pod/api-server-7d4b8c9f6-xkj2p \
8080:8080 \
-n production
# Forward to a Service (load balances across all pods)
kubectl port-forward \
service/api-server \
8080:80 \
-n productionAfter running this command, http://localhost:8080 forwards to the pod. The forwarding runs until you press Ctrl+C. It is not suitable for production traffic — only for development and debugging.
Port-forwarding to a Service rather than a specific pod is more stable because the target pod can be replaced without breaking your tunnel. Use the Service form unless you specifically need to reach a single pod.
Reading events for debugging
When a pod fails to start or behaves unexpectedly, the Kubernetes events system records what happened. Events are the first place to look before diving into logs.
# Events for a specific pod
kubectl describe pod api-server-7d4b8c9f6-xkj2p -n production
# Look for the "Events:" section at the bottom of the output
# All events in a namespace, newest last
kubectl get events \
-n production \
--sort-by='.lastTimestamp'
# Watch events live
kubectl get events -n production -wCommon events and what they mean:
- FailedScheduling — no node has enough CPU/memory to fit the pod. Check resource requests and node capacity.
- ImagePullBackOff / ErrImagePull — the image cannot be pulled. Check the image name, tag, and that the AKS cluster has pull access to the container registry.
- CrashLoopBackOff — the container starts and immediately crashes, repeatedly. Check logs with
—previousflag. - OOMKilled — the container exceeded its memory limit. Increase the limit or fix the memory leak.
# Grant AKS pull access to ACR if you get ImagePullBackOff
az aks update \
--resource-group myResourceGroup \
--name myAKSCluster \
--attach-acr myContainerRegistryUseful kubectl get flags
The get command has several output flags that change what information is returned:
# Wide output — shows node name and IP for pods
kubectl get pods -o wide -n production
# YAML output — full resource definition
kubectl get deployment api-server -n production -o yaml
# JSON output — useful for piping to jq
kubectl get pods -n production -o json | \
jq '.items[].metadata.name'
# Custom columns — show only what you need
kubectl get pods -n production \
-o custom-columns=\
'NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName'
# Watch mode — refreshes every 2 seconds
kubectl get pods -n production -wCommon mistakes
- Using kubectl delete and recreate instead of apply. Deleting and recreating a Deployment causes downtime — all pods are terminated before new ones start. Use
kubectl applyorkubectl set imagefor updates, which trigger a rolling update that maintains availability. - Forgetting namespace flags. kubectl defaults to the
defaultnamespace. If your workloads run in a custom namespace likeproduction, every command needs-n production. Set a default namespace for your context to avoid confusion:kubectl config set-context —current —namespace=production. - Editing live resources with kubectl edit instead of updating the manifest.
kubectl editlets you change a live resource directly, but those changes are not reflected in your YAML files. The nextkubectl applyfrom your file will overwrite them. Always make changes in the manifest file and apply it — treat the YAML files as the source of truth.
Summary
- Use
az aks get-credentialsto configure kubectl for your AKS cluster, then usekubectl apply -ffor all deployments and updates. - A Deployment manifest with resource requests, limits, and health probes is the minimum viable configuration for a production workload.
- Rolling updates via
kubectl set imageor re-applying a manifest replace pods gradually;kubectl rollout undoreverts to the previous version instantly. - For debugging, check events first with
kubectl describeandkubectl get events, then check logs withkubectl logs —previousfor crashed containers.
Frequently asked questions
What is the difference between kubectl apply and kubectl create?
kubectl create is imperative — it creates a resource and fails if one already exists. kubectl apply is declarative — it creates the resource if it doesn't exist or updates it if it does by comparing the desired state in your file against the live state. Use apply for all production workflows so the same command works for initial deployment and updates.
How do I connect kubectl to my AKS cluster?
Run az aks get-credentials --resource-group myRG --name myCluster. This merges the cluster credentials into your local ~/.kube/config file and sets it as the active context. You can then run kubectl commands against that cluster.
Can I run kubectl exec on a production pod to debug it?
You can, but it requires RBAC permission to exec into pods. In production, prefer reading logs and describing events rather than exec. If you must exec, use it read-only and restrict the RBAC role to only the namespaces and pod selectors that need it.