{"id":71479,"date":"2025-07-21T08:17:37","date_gmt":"2025-07-21T16:17:37","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/devops\/?p=71479"},"modified":"2025-07-30T14:36:59","modified_gmt":"2025-07-30T22:36:59","slug":"azure-developer-cli-from-dev-to-prod-with-one-click","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-one-click\/","title":{"rendered":"Azure Developer CLI: From Dev to Prod with One Click"},"content":{"rendered":"<p>This post walks through how to implement a <strong>&#8220;build once, deploy everywhere&#8221;<\/strong> pattern using <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/developer\/azure-developer-cli\/overview\" target=\"_blank\">Azure Developer CLI (azd)<\/a> that provisions environment-specific infrastructure and promotes applications from dev to prod with the same build artifacts. You&#8217;ll learn how to use conditional Bicep deployment, environment variable injection, package preservation across environments, and automated CI\/CD promotion from development to production.<\/p>\n<h2>Environment-Specific Infrastructure<\/h2>\n<p>When deploying applications across environments, different requirements emerge:<\/p>\n<table>\n<thead>\n<tr>\n<th>Component<\/th>\n<th>Development<\/th>\n<th>Production<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Networking<\/strong><\/td>\n<td>Public access<\/td>\n<td>VNet integration + Private endpoints<\/td>\n<\/tr>\n<tr>\n<td><strong>Storage<\/strong><\/td>\n<td>Public with restrictions<\/td>\n<td>Private endpoints only<\/td>\n<\/tr>\n<tr>\n<td><strong>App Service Plan<\/strong><\/td>\n<td>B2 Basic<\/td>\n<td>S1 Standard<\/td>\n<\/tr>\n<tr>\n<td><strong>Security<\/strong><\/td>\n<td>Managed identity<\/td>\n<td>Enhanced network isolation<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Rather than maintaining separate infrastructure templates or complex configuration files for each environment, we can use a single set of Bicep files that adapt based on an environment variable. This approach eliminates infrastructure drift while keeping deployments simple and consistent.<\/p>\n<h2>Set up resources based on an environment variable<\/h2>\n<p>To conditionally provision resources based on environment type, azd leverages an environment variable <code>AZURE_ENV_TYPE<\/code> to make decisions at deployment time.<\/p>\n<p>Behind the scenes, azd passes <code>AZURE_ENV_TYPE<\/code> to Bicep as the <code>envType<\/code> parameter:<\/p>\n<pre><code class=\"bicep\">@description('Environment type - determines networking configuration (dev\/test\/prod)')\n@allowed(['dev', 'test', 'prod'])\nparam envType string = 'dev'\n<\/code><\/pre>\n<p>This parameter then drives conditional resource deployment in Bicep:<\/p>\n<p><strong>For Network Deployment:<\/strong><\/p>\n<pre><code class=\"bicep\">\/\/ Deploy network infrastructure only for production environments\nmodule network '.\/network.bicep' = if (envType == 'prod') {\n  name: 'networkDeployment'\n  params: {\n    location: location\n    tags: tags\n    abbrs: abbrs\n    resourceToken: resourceToken\n  }\n}\n<\/code><\/pre>\n<p><strong>Similarly for Storage Configuration:<\/strong><\/p>\n<pre><code class=\"bicep\">module storageAccount 'br\/public:avm\/res\/storage\/storage-account:0.17.2' = {\n  name: 'storageAccount'\n  params: {\n    name: storageAccountName\n    allowSharedKeyAccess: false\n    publicNetworkAccess: envType == 'prod' ? 'Disabled' : 'Enabled'\n    networkAcls: envType == 'prod' ? {\n      defaultAction: 'Deny'\n      virtualNetworkRules: []\n      bypass: 'AzureServices'\n    } : {\n      defaultAction: 'Allow'\n      virtualNetworkRules: []\n      bypass: 'AzureServices'\n    }\n    \/\/ ... other configuration\n  }\n}\n<\/code><\/pre>\n<p><strong>Finally for App Service Plan Sizing:<\/strong><\/p>\n<pre><code class=\"bicep\">sku: {\n  name: envType == 'prod' ? 'S1' : 'B2'\n  tier: envType == 'prod' ? 'Standard' : 'Basic'\n}\n<\/code><\/pre>\n<h2>Enhance default CI\/CD workflow<\/h2>\n<p>azd includes a powerful command to bootstrap CI\/CD pipeline:<\/p>\n<pre><code class=\"bash\">azd pipeline config\n<\/code><\/pre>\n<p>This generates a basic workflow. However, for dev-to-prod promotion, enhance it with these steps:<\/p>\n<blockquote>\n<p><strong>\u26a0\ufe0f Important Update<\/strong><\/p>\n<p><strong>Updated 7\/30\/2025<\/strong>: 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. We have since updated both the <a href=\"https:\/\/github.com\/puicchan\/azd-dev-prod-appservice-storage\">repository<\/a> and the steps below to use GitHub Action Artifacts.<\/p>\n<\/blockquote>\n<p><strong>1&#46; Package Once:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Package Application\n  run: |\n    mkdir -p .\/dist\n    azd package app --output-path .\/dist\/app-package.zip\n    echo \"\u2705 Application packaged successfully\"\n<\/code><\/pre>\n<p><strong>2&#46; Upload Application Package:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Upload Application Package\n   uses: actions\/upload-artifact@v4\n     with:\n       name: app-package\n       path: .\/dist\/app-package.zip\n       retention-days: 30\n<\/code><\/pre>\n<p><strong>3&#46; Deploy to Dev:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Deploy to Development\n  run: azd deploy app --from-package .\/dist\/app-package.zip --no-prompt\n<\/code><\/pre>\n<p><strong>4&#46; Validation Gate:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Validate Application\n  run: |\n    echo \"\ud83d\udd0d Validating application in development environment...\"\n    # TODO: Add your validation logic 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; Download Application Package:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Download Application Package\n   uses: actions\/download-artifact@v4\n     with:\n       name: app-package\n       path: .\/prod-deploy\n<\/code><\/pre>\n<p><strong>6&#46; Set environment variables:<\/strong><\/p>\n<pre><code class=\"yaml\">- name: Promote to Production\n  run: |\n    # Create production environment name by replacing -dev with -prod, or adding -prod if no -dev suffix\n    PROD_ENV_NAME=\"${AZURE_ENV_NAME%-dev}-prod\"\n    echo \"Production environment name: $PROD_ENV_NAME\"\n\n    # Set environment variables for this step\n    export AZURE_ENV_NAME=\"$PROD_ENV_NAME\"\n    export AZURE_ENV_TYPE=\"prod\"\n<\/code><\/pre>\n<p><strong>7&#46; Deploy to Prod:<\/strong><\/p>\n<pre><code class=\"yaml\"> # Use the artifact package - true \"build once, deploy everywhere\"\n   PACKAGE_PATH=\".\/prod-deploy\/app-package.zip\"  \n\n   if [ -f \"$PACKAGE_PATH\" ]; then\n     echo \"\ud83d\ude80 Deploying to production using artifact package: $PACKAGE_PATH\"\n     azd deploy app --from-package \"$PACKAGE_PATH\" --no-prompt\n     echo \"\u2705 Production deployment completed successfully\"\n   else\n      echo \"\u274c Package artifact not found - falling back to regular deployment\"\n      azd deploy --no-prompt\n   fi\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><strong>Watch the walkthrough:<\/strong> See this entire process in action in the video below that shows the complete setup and deployment flow.<\/p>\n<p><iframe src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2025\/07\/devToprodWithOneClick-1.mp4\" width=\"640\" height=\"360\" frameborder=\"0\" scrolling=\"no\" allowfullscreen title=\"devToprodWithOneClick.mp4\"><\/iframe><\/p>\n<h3>Step-by-Step Setup<\/h3>\n<p>Follow these steps to set up both development and production environments, then configure the CI\/CD pipeline:<\/p>\n<p><strong>1&#46; Initialize project from the template<\/strong><\/p>\n<pre><code class=\"bash\">azd init -t https:\/\/github.com\/puicchan\/azd-dev-prod-appservice-storage\n<\/code><\/pre>\n<p>This downloads the complete implementation with all Bicep files and <strong>enhanced<\/strong> GitHub Actions workflow.<\/p>\n<p><strong>2&#46; Set Up Development Environment<\/strong><\/p>\n<pre><code class=\"bash\">azd up\n<\/code><\/pre>\n<p>When prompted for the environment name, use <code>myproj-dev<\/code> (or your preferred naming pattern with <code>-dev<\/code> suffix).<\/p>\n<p><strong>Note<\/strong>: The default <code>envType<\/code> is <code>dev<\/code>, so you don&#8217;t need to set the <code>AZURE_ENV_TYPE<\/code> environment variable for development. The infrastructure will automatically provision with public access and cost-optimized resources.<\/p>\n<p><strong>3&#46; Set Up Production Environment<\/strong><\/p>\n<p>Create and configure the production environment:<\/p>\n<pre><code class=\"bash\"># Create new production environment\nazd env new myproj-prod\n\n# Set environment type to production\nazd env set AZURE_ENV_TYPE prod\n\n# Deploy production environment\nazd up\n<\/code><\/pre>\n<p>This provisions production infrastructure with VNet integration, private endpoints, and enhanced security. Note that running <code>azd up<\/code> deploys the app to the production infrastructure as well. This is for demonstration purpose plus we are still in development stage. In a real-world scenario, you&#8217;d probably never deploy your app directly if it is already in production.<\/p>\n<p><strong>4&#46; Switch Back to Development Environment<\/strong><\/p>\n<pre><code class=\"bash\">azd env select myproj-dev\n<\/code><\/pre>\n<p>You&#8217;re now ready to develop and test in the development environment.<\/p>\n<p><strong>5&#46; Make Code Changes<\/strong><\/p>\n<p>Edit your application code (e.g., modify <code>app\/templates\/index.html<\/code> or <code>app.py<\/code>) to test the promotion workflow.<\/p>\n<p><strong>6&#46; Configure CI\/CD Pipeline<\/strong><\/p>\n<pre><code class=\"bash\">azd pipeline config\n<\/code><\/pre>\n<p>The GitHub Actions workflow is enhanced with dev-to-prod promotion logic. Specifically, the pipeline will:<\/p>\n<ul>\n<li>Deploy and validate in development (&#96;myproj-dev&#96;)<\/li>\n<li>Automatically promote to production (&#96;myproj-prod&#96;) using the same package<\/li>\n<li>Handle environment naming conversion automatically<\/li>\n<\/ul>\n<p>Once configured, every push to the main branch will trigger the automated dev-to-prod promotion pipeline!<\/p>\n<h2>Conclusion<\/h2>\n<p>In summary, this pattern combines conditional Bicep deployment, package preservation, and smart environment naming to create reliable dev-to-prod promotions with the same build artifacts.<\/p>\n<p>Questions about implementation or want to share your own approach? Join the discussion <a href=\"https:\/\/github.com\/Azure\/azure-dev\/discussions\/5447\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post walks through how to implement a &#8220;build once, deploy everywhere&#8221; pattern using Azure Developer CLI (azd) that provisions environment-specific infrastructure and promotes applications from dev to prod with the same build artifacts. You&#8217;ll learn how to use conditional Bicep deployment, environment variable injection, package preservation across environments, and automated CI\/CD promotion from development [&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":[],"class_list":["post-71479","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-azure-developer-cli-azd","category-ci","category-devops"],"acf":[],"blog_post_summary":"<p>This post walks through how to implement a &#8220;build once, deploy everywhere&#8221; pattern using Azure Developer CLI (azd) that provisions environment-specific infrastructure and promotes applications from dev to prod with the same build artifacts. You&#8217;ll learn how to use conditional Bicep deployment, environment variable injection, package preservation across environments, and automated CI\/CD promotion from development [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/71479","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=71479"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/71479\/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=71479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/categories?post=71479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/tags?post=71479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}