How do you deploy to Kubernetes from Azure DevOps using YAML pipelines with kubectl or Helm tasks?
Quick Answer
Azure DevOps deploys to Kubernetes using the KubernetesManifest@1 task for kubectl operations or the HelmDeploy@0 task for Helm chart releases. Both require a Kubernetes Service Connection for cluster authentication. The pipeline builds a container image, pushes it to a registry, then applies manifests or upgrades Helm releases with the new image tag, typically using deployment jobs with environment tracking.
Detailed Answer
Think of deploying to Kubernetes from Azure DevOps like a shipping company dispatching packages from a central warehouse to multiple distribution centers. The warehouse (pipeline) packages goods (builds container images), labels them with tracking numbers (image tags), and dispatches them to specific centers (clusters) using either manual delivery instructions (kubectl manifests) or pre-arranged delivery templates (Helm charts). The dispatch system needs authentication credentials (service connection) for each center, and tracks every shipment for audit purposes.
The deployment flow follows a consistent pattern regardless of whether you use kubectl or Helm. First, the pipeline builds the container image and pushes it to a registry (ACR, ECR, Docker Hub). Then, a deployment job in a subsequent stage authenticates to the target Kubernetes cluster using a Service Connection and either applies raw manifests with the new image tag substituted, or upgrades a Helm release with the new image tag passed as a value override. The deployment job references an Azure DevOps Environment that tracks deployment history and can enforce approval gates before production clusters.
The KubernetesManifest@1 task handles kubectl-based deployments with additional intelligence beyond raw kubectl apply. It supports image substitution (automatically replacing image references in manifests with the pipeline's built image tag), deployment strategies (canary with traffic splitting, rolling updates), and automatic rollback on failure. The task connects to the cluster using the configured Kubernetes Service Connection and applies manifests in the specified namespace. For teams that maintain raw Kubernetes YAML manifests in their repository alongside application code, this task provides a straightforward deployment path.
The HelmDeploy@0 task manages Helm chart installations and upgrades. It supports installing charts from Helm repositories, OCI registries, or local chart directories within the repository. The typical pattern is helm upgrade --install with --set flags that override values like the image tag, replica count, and environment-specific configuration. Helm's release management provides atomic deployments with automatic rollback on failure, release history for auditing, and templated manifests that reduce YAML duplication across environments. The task handles Helm repository authentication, chart dependency updates, and value file overrides.
Namespace strategy and environment separation are critical architectural decisions. The simplest approach deploys different environments to different namespaces within the same cluster (payments-dev, payments-staging, payments-prod namespaces). A more isolated approach uses separate clusters per environment. The pipeline handles this through different Service Connections and namespace parameters per stage. Variable groups store environment-specific Helm values, and stage conditions ensure production deployment only occurs from the main branch after staging validation.
The production gotcha is image tag mutability and deployment verification. Using mutable tags like 'latest' means kubectl sees no manifest change (the tag string is the same) and does not trigger a rollout. Always use immutable tags like the build ID or git SHA. After applying manifests, the pipeline should verify the rollout succeeded using kubectl rollout status, which blocks until all pods are running the new version or a timeout is reached. Without this verification, the pipeline reports success while pods are crash-looping with the new image. Another issue is Helm value precedence: values from --set flags override values files, and the order of multiple --set flags matters when the same key appears multiple times.