Building on our previous post about implementing dev-to-prod promotion with GitHub Actions, this follow-up demonstrates the same “build once, deploy everywhere” pattern using Azure DevOps Pipelines. You’ll learn how to leverage Azure DevOps YAML pipelines with Azure Developer CLI (azd). This approach ensures consistent, reliable deployments across environments.
Environment-Specific Infrastructure
The infrastructure approach is identical to our previous GitHub Actions implementation. It uses conditional Bicep deployment with a single envType
parameter. This drives environment-specific resource configuration. The same Bicep templates work seamlessly across both CI/CD platforms.
For the complete infrastructure setup details, refer to the GitHub Actions post.
From File Backup to Pipeline Artifacts
The original approach used local file backups (copying zip files within the same job). However, the community pointed out that using native CI/CD artifact systems is more idiomatic. This provides several key advantages:
- Cross-job compatibility: Artifacts work seamlessly across multiple jobs and stages.
- Automatic cleanup: The platform handles retention policies automatically.
- Better traceability: Artifacts are visible in the platform UI with download history.
- Platform integration: Native features with built-in security and access controls.
This artifact-based approach represents the industry standard for “build once, deploy everywhere” patterns. It works across modern CI/CD platforms. We have updated the original GitHub Actions implementation to use the same pattern demonstrated in this Azure DevOps version.
Azure DevOps Pipeline Enhancement
Azure DevOps pipelines require a different approach than GitHub Actions. However, they achieve the same outcome. We’ll demonstrate a multi-stage pipeline that provides proper separation of concerns and enterprise-ready deployment patterns. This staged approach offers better isolation, approval workflows, and traceability compared to single-job pipelines.
The enhanced pipeline follows a three-stage structure:
1. Pipeline Structure
The multi-stage pipeline uses separate stages for build, development deployment, and production promotion:
# Run when commits are pushed to main
trigger:
- main
pool:
vmImage: ubuntu-latest
stages:
- stage: build_and_test
- stage: deploy_development
dependsOn: build_and_test
- stage: promote_to_Prod
dependsOn: deploy_development
2. Build and Package Stage
The first stage focuses solely on building and packaging the application for deployment:
- stage: build_and_test
jobs:
- job: buildAndPackage
pool:
vmImage: ubuntu-latest
steps:
- task: Bash@3
displayName: Install azd
inputs:
targetType: 'inline'
script: |
curl -fsSL https://aka.ms/install-azd.sh | bash
- task: PowerShell@2
displayName: Configure AZD to Use AZ CLI Authentication.
inputs:
targetType: inline
script: |
azd config set auth.useAzCliAuth "true"
pwsh: true
- task: AzureCLI@2
displayName: Package Application
inputs:
azureSubscription: azconnection
scriptType: bash
scriptLocation: inlineScript
keepAzSessionActive: true
inlineScript: |
mkdir -p ./dist
azd package app --output-path ./dist/app-package.zip --no-prompt
echo "✅ Application packaged successfully"
- task: PublishPipelineArtifact@1
displayName: Upload Package Artifact
inputs:
targetPath: './dist/app-package.zip'
artifact: 'app-package'
publishLocation: 'pipeline'
3. Deploy to Development Stage
The second stage provisions development infrastructure and deploys the packaged application:
- stage: deploy_development
dependsOn: build_and_test
jobs:
- job: deployToDevelopment
pool:
vmImage: ubuntu-latest
steps:
- task: Bash@3
displayName: Install azd
inputs:
targetType: 'inline'
script: |
curl -fsSL https://aka.ms/install-azd.sh | bash
- task: PowerShell@2
displayName: Configure AZD to Use AZ CLI Authentication.
inputs:
targetType: inline
script: |
azd config set auth.useAzCliAuth "true"
pwsh: true
- task: AzureCLI@2
displayName: Provision DEV Infrastructure
inputs:
azureSubscription: azconnection
scriptType: bash
scriptLocation: inlineScript
keepAzSessionActive: true
inlineScript: |
azd provision --no-prompt
- task: DownloadPipelineArtifact@2
displayName: Download Package Artifact
inputs:
buildType: 'current'
artifactName: 'app-package'
targetPath: './artifacts'
- task: AzureCLI@2
displayName: Deploy to Development
inputs:
azureSubscription: azconnection
scriptType: bash
scriptLocation: inlineScript
keepAzSessionActive: true
inlineScript: |
azd deploy app --from-package ./artifacts/app-package.zip --no-prompt
4. Validation Gate
Add validation checks before promotion to production:
- task: AzureCLI@2
displayName: Validate Application
inputs:
azureSubscription: azconnection
scriptType: bash
scriptLocation: inlineScript
keepAzSessionActive: true
inlineScript: |
echo "🔍 Validating application in development environment..."
# TODO: Add actual validation here
# Examples:
# - Health checks and integration tests
# - Security and compliance scanning
# - Performance validation
sleep 3 # Simulate validation time
echo "✅ Application validation passed"
5. Promote to Production Stage
The final stage uses environment-specific variables to deploy to production:
- stage: promote_to_Prod
dependsOn: deploy_development
jobs:
- job: deployProduction
# use prod settings to override default environment variables
# this variables become ENV VARS for all tasks in this job
variables:
AZURE_ENV_NAME: $(AZURE_PROD_ENV_NAME)
AZURE_ENV_TYPE: $(AZURE_PROD_ENV_TYPE)
AZURE_LOCATION: $(AZURE_PROD_LOCATION)
AZURE_SUBSCRIPTION_ID: $(AZURE_PROD_SUBSCRIPTION_ID)
pool:
vmImage: ubuntu-latest
steps:
- task: Bash@3
displayName: Install azd
inputs:
targetType: 'inline'
script: |
curl -fsSL https://aka.ms/install-azd.sh | bash
- task: PowerShell@2
displayName: Configure AZD to Use AZ CLI Authentication.
inputs:
targetType: inline
script: |
azd config set auth.useAzCliAuth "true"
pwsh: true
- task: DownloadPipelineArtifact@2
displayName: Download Package Artifact
inputs:
buildType: 'current'
artifactName: 'app-package'
targetPath: './artifacts'
- task: AzureCLI@2
displayName: Deploy to PROD
inputs:
azureSubscription: azconnection
scriptType: bash
scriptLocation: inlineScript
keepAzSessionActive: true
inlineScript: |
azd deploy app --from-package ./artifacts/app-package.zip --no-prompt
Try It Out
You can try this approach using the complete implementation here
Watch the walkthrough:
Prerequisites
You’ll need a Personal Access Token (PAT) to set up Azure DevOps pipelines with azd. For detailed guidance on PAT creation and pipeline setup, refer to the Microsoft Learn documentation.
Setup Steps
1. Initialize Project
azd init -t https://github.com/puicchan/azd-dev-prod-appservice-storage
Use environment name like projazdo-dev
.
2. Edit azure.yaml
Make sure you configure Azure DevOps as the CICD tool and add these pipeline variables:
pipeline:
provider: azdo
variables:
- AZURE_PROD_ENV_NAME
- AZURE_PROD_ENV_TYPE
- AZURE_PROD_LOCATION
- AZURE_PROD_SUBSCRIPTION_ID
3. Set Up Development Environment
azd up
4. Set Up Production Environment
We will run azd provision
and rely on Azure Pipeline to deploy the app to production:
azd env new projazdo-prod
azd env set AZURE_ENV_TYPE prod
azd provision
5. Test the Flow
Switch back to Development:
azd env select projazdo-dev
Edit your application code and make sure you run azd env set
to configure the following environment variables the pipeline requires:
- AZURE_PROD_ENV_NAME
- AZURE_PROD_ENV_TYPE
- AZURE_PROD_LOCATION
- AZURE_PROD_SUBSCRIPTION_ID
6. Configure CI/CD Pipeline
azd pipeline config
Go to your Azure Pipelines Organization and check out the pipeline run.
Pro Tip: Enhance Your Pipeline with AI
Need help with Azure DevOps and azd? GitHub Copilot for Azure with Azure MCP can help you enhance your azure-dev.yml
file directly in VS Code.
With the GitHub Copilot for Azure extension installed, and the GitHub Copilot for Azure and Azure MCP tools enabled in Agent mode, GitHub Copilot can:
- Debug pipeline issues: Analyze YAML syntax errors and configuration problems.
- Add validation steps: Suggest health checks, security scans, or integration tests.
- Optimize deployment strategies: Recommend blue-green deployments or canary releases.
- Configure environment-specific logic: Help set up conditional steps for different environments.
Simply ask GitHub Copilot questions like:
- “Add a health check validation step to my Azure DevOps pipeline”
- “How can I add manual approval gates before production deployment?”
Agent mode with GitHub Copilot for Azure provides contextual understanding of your Azure resources. It can suggest pipeline improvements based on your specific infrastructure setup.
Conclusion
This Azure DevOps implementation demonstrates how the “build once, deploy everywhere” pattern translates seamlessly across different CI/CD platforms. The core azd and Bicep logic remains identical. Meanwhile, platform-specific features like service connections and task definitions provide the Azure DevOps-native experience.
Whether you choose GitHub Actions or Azure DevOps, the fundamental approach remains consistent. Conditional infrastructure deployment and package promotion ensure reliable deployments across your development lifecycle.
Questions about implementation or want to share your Azure DevOps approach? Join the discussion here.
0 comments
Be the first to start the discussion.