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 nodes

The 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 production

To 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 production

If 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 production

Viewing 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 production

kubectl 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 production

After 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.

Tip

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 -w

Common 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 —previous flag.
  • 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 myContainerRegistry

Useful 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 -w

Common mistakes

  1. 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 apply or kubectl set image for updates, which trigger a rolling update that maintains availability.
  2. Forgetting namespace flags. kubectl defaults to the default namespace. If your workloads run in a custom namespace like production, every command needs -n production. Set a default namespace for your context to avoid confusion: kubectl config set-context —current —namespace=production.
  3. Editing live resources with kubectl edit instead of updating the manifest. kubectl edit lets you change a live resource directly, but those changes are not reflected in your YAML files. The next kubectl apply from your file will overwrite them. Always make changes in the manifest file and apply it — treat the YAML files as the source of truth.

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.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.