{"id":71600,"date":"2025-08-13T10:29:24","date_gmt":"2025-08-13T18:29:24","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/devops\/?p=71600"},"modified":"2025-08-15T08:57:06","modified_gmt":"2025-08-15T16:57:06","slug":"azure-developer-cli-from-dev-to-prod-with-azure-devops-pipelines","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-azure-devops-pipelines\/","title":{"rendered":"Azure Developer CLI: From Dev to Prod with Azure DevOps Pipelines"},"content":{"rendered":"<p>Building on our previous <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-one-click\/\">post<\/a> about implementing dev-to-prod promotion with GitHub Actions, this follow-up demonstrates the same &#8220;build once, deploy everywhere&#8221; pattern using Azure DevOps Pipelines. You&#8217;ll learn how to leverage Azure DevOps YAML pipelines with <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/overview\">Azure Developer CLI (azd)<\/a>. This approach ensures consistent, reliable deployments across environments.<\/p>\n<h2>Environment-Specific Infrastructure<\/h2>\n<p>The infrastructure approach is identical to our <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-one-click\/\">previous GitHub Actions implementation<\/a>. It uses conditional Bicep deployment with a single <code>envType<\/code> parameter. This drives environment-specific resource configuration. The same Bicep templates work seamlessly across both CI\/CD platforms.<\/p>\n<p>For the complete infrastructure setup details, refer to the <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-one-click\/#set-up-resources-based-on-an-environment-variable\">GitHub Actions post<\/a>.<\/p>\n<h2>From File Backup to Pipeline Artifacts<\/h2>\n<p>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:<\/p>\n<ul>\n<li><strong>Cross-job compatibility<\/strong>: Artifacts work seamlessly across multiple jobs and stages.<\/li>\n<li><strong>Automatic cleanup<\/strong>: The platform handles retention policies automatically.<\/li>\n<li><strong>Better traceability<\/strong>: Artifacts are visible in the platform UI with download history.<\/li>\n<li><strong>Platform integration<\/strong>: Native features with built-in security and access controls.<\/li>\n<\/ul>\n<p>This artifact-based approach represents the industry standard for &#8220;build once, deploy everywhere&#8221; 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.<\/p>\n<h2>Azure DevOps Pipeline Enhancement<\/h2>\n<p>Azure DevOps pipelines require a different approach than GitHub Actions. However, they achieve the same outcome. We&#8217;ll demonstrate a <strong>multi-stage pipeline<\/strong> 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.<\/p>\n<p>The enhanced pipeline follows a three-stage structure:<\/p>\n<p><strong>1&#46; Pipeline Structure<\/strong><\/p>\n<p>The multi-stage pipeline uses separate stages for build, development deployment, and production promotion:<\/p>\n<pre><code class=\"yaml\"># Run when commits are pushed to main\ntrigger:\n  - main\n\npool:\n  vmImage: ubuntu-latest\n\nstages:\n- stage: build_and_test\n- stage: deploy_development\n  dependsOn: build_and_test\n- stage: promote_to_Prod\n  dependsOn: deploy_development\n<\/code><\/pre>\n<p><strong>2&#46; Build and Package Stage<\/strong><\/p>\n<p>The first stage focuses solely on building and packaging the application for deployment:<\/p>\n<pre><code class=\"yaml\">- stage: build_and_test\n  jobs:\n  - job: buildAndPackage\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: Bash@3\n      displayName: Install azd\n      inputs:\n        targetType: 'inline'\n        script: |\n          curl -fsSL https:\/\/aka.ms\/install-azd.sh | bash\n    \n    - task: PowerShell@2\n      displayName: Configure AZD to Use AZ CLI Authentication.\n      inputs:\n        targetType: inline\n        script: |\n          azd config set auth.useAzCliAuth \"true\"\n        pwsh: true\n    \n    - task: AzureCLI@2\n      displayName: Package Application\n      inputs:\n        azureSubscription: azconnection\n        scriptType: bash\n        scriptLocation: inlineScript\n        keepAzSessionActive: true\n        inlineScript: |\n          mkdir -p .\/dist\n          azd package app --output-path .\/dist\/app-package.zip  --no-prompt\n          echo \"\u2705 Application packaged successfully\"\n    \n    - task: PublishPipelineArtifact@1\n      displayName: Upload Package Artifact\n      inputs:\n        targetPath: '.\/dist\/app-package.zip'\n        artifact: 'app-package'\n        publishLocation: 'pipeline'\n<\/code><\/pre>\n<p><strong>3&#46; Deploy to Development Stage<\/strong><\/p>\n<p>The second stage provisions development infrastructure and deploys the packaged application:<\/p>\n<pre><code class=\"yaml\">- stage: deploy_development\n  dependsOn: build_and_test\n  jobs:\n  - job: deployToDevelopment\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: Bash@3\n      displayName: Install azd\n      inputs:\n        targetType: 'inline'\n        script: |\n          curl -fsSL https:\/\/aka.ms\/install-azd.sh | bash\n    \n    - task: PowerShell@2\n      displayName: Configure AZD to Use AZ CLI Authentication.\n      inputs:\n        targetType: inline\n        script: |\n          azd config set auth.useAzCliAuth \"true\"\n        pwsh: true\n    \n    - task: AzureCLI@2\n      displayName: Provision DEV Infrastructure\n      inputs:\n        azureSubscription: azconnection\n        scriptType: bash\n        scriptLocation: inlineScript\n        keepAzSessionActive: true\n        inlineScript: |\n          azd provision --no-prompt\n\n    - task: DownloadPipelineArtifact@2\n      displayName: Download Package Artifact\n      inputs:\n        buildType: 'current'\n        artifactName: 'app-package'\n        targetPath: '.\/artifacts'\n\n    - task: AzureCLI@2\n      displayName: Deploy to Development\n      inputs:\n        azureSubscription: azconnection\n        scriptType: bash\n        scriptLocation: inlineScript\n        keepAzSessionActive: true\n        inlineScript: |\n          azd deploy app --from-package .\/artifacts\/app-package.zip --no-prompt\n<\/code><\/pre>\n<p><strong>4&#46; Validation Gate<\/strong><\/p>\n<p>Add validation checks before promotion to production:<\/p>\n<pre><code class=\"yaml\">    - task: AzureCLI@2\n      displayName: Validate Application\n      inputs:\n        azureSubscription: azconnection\n        scriptType: bash\n        scriptLocation: inlineScript\n        keepAzSessionActive: true\n        inlineScript: |\n          echo \"\ud83d\udd0d Validating application in development environment...\"\n          # TODO: Add actual validation here\n          # Examples:\n          # - Health checks and integration tests\n          # - Security and compliance scanning\n          # - Performance validation\n          sleep 3  # Simulate validation time\n          echo \"\u2705 Application validation passed\"\n<\/code><\/pre>\n<p><strong>5&#46; Promote to Production Stage<\/strong><\/p>\n<p>The final stage uses environment-specific variables to deploy to production:<\/p>\n<pre><code class=\"yaml\">- stage: promote_to_Prod\n  dependsOn: deploy_development\n  jobs:\n  - job: deployProduction\n    # use prod settings to override default environment variables\n    # this variables become ENV VARS for all tasks in this job\n    variables:\n      AZURE_ENV_NAME: $(AZURE_PROD_ENV_NAME)\n      AZURE_ENV_TYPE: $(AZURE_PROD_ENV_TYPE)\n      AZURE_LOCATION: $(AZURE_PROD_LOCATION)\n      AZURE_SUBSCRIPTION_ID: $(AZURE_PROD_SUBSCRIPTION_ID)\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: Bash@3\n      displayName: Install azd\n      inputs:\n        targetType: 'inline'\n        script: |\n          curl -fsSL https:\/\/aka.ms\/install-azd.sh | bash\n    \n    - task: PowerShell@2\n      displayName: Configure AZD to Use AZ CLI Authentication.\n      inputs:\n        targetType: inline\n        script: |\n          azd config set auth.useAzCliAuth \"true\"\n        pwsh: true\n    \n    - task: DownloadPipelineArtifact@2\n      displayName: Download Package Artifact\n      inputs:\n        buildType: 'current'\n        artifactName: 'app-package'\n        targetPath: '.\/artifacts'\n\n    - task: AzureCLI@2\n      displayName: Deploy to PROD\n      inputs:\n        azureSubscription: azconnection\n        scriptType: bash\n        scriptLocation: inlineScript\n        keepAzSessionActive: true\n        inlineScript: |\n          azd deploy app --from-package .\/artifacts\/app-package.zip --no-prompt\n<\/code><\/pre>\n<h2>Try It Out<\/h2>\n<p>You can try this approach using the complete implementation <a href=\"https:\/\/github.com\/puicchan\/azd-dev-prod-appservice-storage\">here<\/a>.<\/p>\n<p>Watch the walkthrough:<\/p>\n<p><iframe loading=\"lazy\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2025\/08\/devToprodWithOneClick-azdo-1.mp4\" width=\"640\" height=\"360\" frameborder=\"0\" scrolling=\"no\" allowfullscreen title=\"devToprodWithOneClick-azdo-1.mp4\"><\/iframe><\/p>\n<h3>Prerequisites<\/h3>\n<p>You&#8217;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 <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/developer\/azure-developer-cli\/pipeline-azure-pipelines\">Microsoft Learn documentation<\/a>.<\/p>\n<h3>Setup Steps<\/h3>\n<p><strong>1&#46; Initialize Project<\/strong><\/p>\n<pre><code class=\"bash\">azd init -t https:\/\/github.com\/puicchan\/azd-dev-prod-appservice-storage\n<\/code><\/pre>\n<p>Use environment name like <code>projazdo-dev<\/code>.<\/p>\n<p><strong>2&#46; Edit azure.yaml<\/strong><\/p>\n<p>Make sure you configure Azure DevOps as the CICD tool and add these pipeline variables:<\/p>\n<pre><code class=\"yaml\">pipeline:\n  provider: azdo\n  variables:\n    - AZURE_PROD_ENV_NAME\n    - AZURE_PROD_ENV_TYPE\n    - AZURE_PROD_LOCATION\n    - AZURE_PROD_SUBSCRIPTION_ID\n<\/code><\/pre>\n<p><strong>3&#46; Set Up Development Environment<\/strong><\/p>\n<pre><code class=\"bash\">azd up\n<\/code><\/pre>\n<p><strong>4&#46; Set Up Production Environment<\/strong><\/p>\n<p>We will run <code>azd provision<\/code> and rely on Azure Pipeline to deploy the app to production:<\/p>\n<pre><code class=\"bash\">azd env new projazdo-prod\nazd env set AZURE_ENV_TYPE prod\nazd provision\n<\/code><\/pre>\n<p><strong>5&#46; Test the Flow<\/strong><\/p>\n<p>Switch back to Development:<\/p>\n<pre><code class=\"bash\">azd env select projazdo-dev\n<\/code><\/pre>\n<p>Edit your application code and make sure you run <code>azd env set<\/code> to configure the following environment variables the pipeline requires:<\/p>\n<ul>\n<li>AZURE_PROD_ENV_NAME<\/li>\n<li>AZURE_PROD_ENV_TYPE<\/li>\n<li>AZURE_PROD_LOCATION<\/li>\n<li>AZURE_PROD_SUBSCRIPTION_ID <\/li>\n<\/ul>\n<p><strong>6&#46; Configure CI\/CD Pipeline<\/strong><\/p>\n<pre><code class=\"bash\">azd pipeline config\n<\/code><\/pre>\n<p>Go to your Azure Pipelines Organization and check out the pipeline run.<\/p>\n<h2>Pro Tip: Enhance Your Pipeline with AI<\/h2>\n<p>Need help with Azure DevOps and azd? <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-azuretools.vscode-azure-github-copilot\">GitHub Copilot for Azure<\/a> with <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-mcp-server\/overview\">Azure MCP<\/a> can help you enhance your <code>azure-dev.yml<\/code> file directly in VS Code.<\/p>\n<p>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:<\/p>\n<ul>\n<li><strong>Debug pipeline issues<\/strong>: Analyze YAML syntax errors and configuration problems.<\/li>\n<li><strong>Add validation steps<\/strong>: Suggest health checks, security scans, or integration tests.<\/li>\n<li><strong>Optimize deployment strategies<\/strong>: Recommend blue-green deployments or canary releases.<\/li>\n<li><strong>Configure environment-specific logic<\/strong>: Help set up conditional steps for different environments.<\/li>\n<\/ul>\n<p>Simply ask GitHub Copilot questions like:<\/p>\n<ul>\n<li>&#8220;Add a health check validation step to my Azure DevOps pipeline&#8221;<\/li>\n<li>&#8220;How can I add manual approval gates before production deployment?&#8221;<\/li>\n<\/ul>\n<p>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.<\/p>\n<h2>Conclusion<\/h2>\n<p>This Azure DevOps implementation demonstrates how the &#8220;build once, deploy everywhere&#8221; 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.<\/p>\n<p>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.<\/p>\n<p>Questions about implementation or want to share your Azure DevOps approach? Join the discussion <a href=\"https:\/\/github.com\/Azure\/azure-dev\/discussions\/5447\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Building on our previous post about implementing dev-to-prod promotion with GitHub Actions, this follow-up demonstrates the same &#8220;build once, deploy everywhere&#8221; pattern using Azure DevOps Pipelines. You&#8217;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 [&hellip;]<\/p>\n","protected":false},"author":111321,"featured_media":71540,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[224,7300,226,1],"tags":[7302,7301],"class_list":["post-71600","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-azure-developer-cli-azd","category-ci","category-devops","tag-azd","tag-azure-developer-cli"],"acf":[],"blog_post_summary":"<p>Building on our previous post about implementing dev-to-prod promotion with GitHub Actions, this follow-up demonstrates the same &#8220;build once, deploy everywhere&#8221; pattern using Azure DevOps Pipelines. You&#8217;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 [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/71600","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/users\/111321"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/comments?post=71600"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/71600\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/media\/71540"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/media?parent=71600"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/categories?post=71600"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/tags?post=71600"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}