YAML pipelines in Azure DevOps have taken over. The classic visual editor is obsolete. In 2020, every pipeline worth its salt is written in YAML. Here's what matters.

Templates make YAML pipelines efficient. Define common patterns once (build, test, deploy, security scan) and reuse them across projects. You get step templates, job templates, and parameterized variants. Platform teams set the standard structure. App teams extend it without duplicating boilerplate code.

For example, a template for a Node.js build can include steps for npm install, npm test, and npm run build, with parameters for the working directory and output folder. This template can then be reused across multiple projects, reducing duplication and improving consistency. I've seen this approach reduce the number of unique build steps from over 500 to fewer than 20, making it much easier to maintain and update our pipelines.

Environments in Azure DevOps define deployment targets like Kubernetes namespaces or VM groups. They enforce required approvals and track deployment history. A pipeline stage targeting an environment must pass human review first. This replaces the old manual approval gates with a structured, auditable workflow.

Variable groups store environment-specific configuration. Key Vault-backed groups pull secrets at runtime without storing them in Azure DevOps. Secrets stay in Key Vault, and pipeline variables update automatically when Key Vault secrets rotate. No need to modify pipelines when secrets change. We use Azure Key Vault to store our database connection strings and API keys, and our pipelines fetch these values at runtime using the AzureKeyVault task.

I've seen cases where a secret rotation would previously require updating multiple pipelines, but with Key Vault integration, this is now a simple matter of updating the secret in one place. This has reduced the time spent on secret management from several hours to just a few minutes, and has also reduced the risk of accidentally exposing sensitive information.

Service connections control pipeline permissions. Each connection should have only the RBAC permissions needed for its task. An AKS deployment connection needs namespace-level access, not cluster-admin. Separate connections for dev, staging, and prod prevent accidental production deployments. We use Azure Active Directory to manage our service connections, and have implemented a least privilege approach to ensure that each connection has only the permissions it needs to perform its task.

Template reuse prevents sprawl. If every team copies the same build steps into their YAML files, you'll end up with 100 near-identical definitions. Centralized templates with parameters let teams customize without rewriting. For instance, we have a template for deploying to Azure App Service, which includes parameters for the app service name and resource group. This template can be reused across multiple projects, reducing the risk of errors and inconsistencies.

Environment approvals create guardrails. If your pipeline targets production, it must pass through a review step. The environment tracks who approved the deployment, when it happened, and which build was deployed. No more guessing who broke prod. We've implemented environment approvals for our production deployments, and have seen a significant reduction in the number of accidental deployments to production.

Key Vault integration simplifies secrets management. You don't store secrets in Azure DevOps at all. The pipeline fetches them from Key Vault during runtime. Rotate a secret in Key Vault, and all pipelines using it get the new value automatically. This has been a major improvement over our previous approach, which involved storing secrets in Azure DevOps and updating them manually whenever they changed.

Least privilege applies to service connections. A dev pipeline shouldn't have access to production resources. Create environment-specific connections with minimal permissions. If a dev pipeline misbehaves, it can't accidentally delete production data. We've implemented a strict least privilege approach to our service connections, and have seen a significant reduction in the risk of accidental data loss or corruption.

Parameterized templates add flexibility. You define a standard deployment template with parameters for environment name and region. Each app team can customize the values without rewriting the core logic. This scales better than hardcoding everything. For example, we have a template for deploying to Azure Kubernetes Service, which includes parameters for the cluster name and namespace. This template can be reused across multiple projects, reducing the risk of errors and inconsistencies.

I've seen cases where a team would previously have to create a custom deployment script for each environment, but with parameterized templates, this is no longer necessary. The team can simply customize the template parameters for each environment, and the pipeline will take care of the rest. This has reduced the time spent on deployment scripting from several days to just a few hours, and has also improved the consistency and reliability of our deployments.