{"id":2737,"date":"2025-11-04T22:45:46","date_gmt":"2025-11-04T22:45:46","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/11\/04\/azure-developer-cli-azure-container-apps-dev-to-prod-deployment-with-layered-infrastructure\/"},"modified":"2025-11-04T22:45:46","modified_gmt":"2025-11-04T22:45:46","slug":"azure-developer-cli-azure-container-apps-dev-to-prod-deployment-with-layered-infrastructure","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/11\/04\/azure-developer-cli-azure-container-apps-dev-to-prod-deployment-with-layered-infrastructure\/","title":{"rendered":"Azure Developer CLI: Azure Container Apps Dev-to-Prod Deployment with Layered Infrastructure"},"content":{"rendered":"<p>This post walks through how to implement \u201cbuild once, deploy everywhere\u201d patterns using Azure Container Apps with the new <code>azd publish<\/code> and layered infrastructure features in Azure Developer CLI v1.20.0. You\u2019ll learn how to deploy the same containerized application across multiple environments with proper separation of concerns.<\/p>\n<p>This is the third installment in our Azure Developer CLI series, building on our previous explorations: \u2013 <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-one-click\/\">Azure App Service and GitHub Actions<\/a> \u2013 <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-from-dev-to-prod-with-azure-devops-pipelines\/\">Azure DevOps Pipelines<\/a><\/p>\n<h2>Build once, deploy everywhere<\/h2>\n<h3>The challenge we\u2019re solving<\/h3>\n<p>If you\u2019ve worked with containers in production, you\u2019ve probably run into this: <code>azd deploy<\/code> bundles everything together\u2014building your container, pushing to a registry, and deploying\u2014all in one go. While this is super convenient for development, it creates some headaches for production scenarios:<\/p>\n<ul>\n<li>You want to use a <strong>single Azure Container Registry (ACR)<\/strong> across all your environments<\/li>\n<li>You need to <strong>build once and deploy everywhere<\/strong> without rebuilding containers <\/li>\n<li>You want <strong>security controls<\/strong> around which specific container versions get deployed to production<\/li>\n<li>You need the <strong>flexibility<\/strong> to deploy the same container with different configurations per environment<\/li>\n<\/ul>\n<h3>Learning from our previous posts in this series<\/h3>\n<p>After writing about dev-to-prod patterns with Azure App Service in our first two blog posts, we realized that Azure Container Apps support in azd had some limitations that prevented teams from implementing the same \u201cbuild once, deploy everywhere\u201d patterns effectively. The azd team addressed these gaps in the recent releases.<\/p>\n<p>Azure Developer CLI v1.20.0 introduces two capabilities that solve these challenges:<\/p>\n<h4>1. <strong>Separated Container Operations<\/strong><\/h4>\n<ul>\n<li><strong><code>azd publish<\/code><\/strong>: Builds and pushes containers to your registry<\/li>\n<li><strong><code>azd deploy --from-package<\/code><\/strong>: Deploys specific container versions to environments (without rebuilding)<\/li>\n<\/ul>\n<h4>2. <strong>Layered Infrastructure (Alpha Feature)<\/strong><\/h4>\n<ul>\n<li>Deploy infrastructure in <strong>sequential layers<\/strong> with proper dependency management<\/li>\n<li>Share resources like ACR across environments while keeping environment-specific stuff separate<\/li>\n<li>Outputs from earlier layers automatically become inputs for later layers<\/li>\n<\/ul>\n<p>I\u2019ll show you how this works using a <a href=\"https:\/\/github.com\/puicchan\/azd-dev-prod-aca-storage\">Flask application example<\/a> that I migrated from Azure App Service to Azure Container Apps.<\/p>\n<h2>The sample application<\/h2>\n<h3>What we\u2019re building<\/h3>\n<p>The sample application is a simple Flask-based file manager that demonstrates the key concepts:<\/p>\n<ul>\n<li><strong>What it does<\/strong>: Upload files, list them, and view them (all backed by Azure Blob Storage)<\/li>\n<li><strong>Security approach<\/strong>: Uses Azure Managed Identity (no connection strings stored anywhere)<\/li>\n<\/ul>\n<h3>How the infrastructure is organized<\/h3>\n<p>Rather than cramming everything into one big template, I\u2019ve organized this using a layered approach that keeps shared stuff separate from environment-specific resources:<\/p>\n<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                     Shared Resources                            \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502\n\u2502  \u2502 Resource Group: rg-acr-shared                               \u2502\u2502\n\u2502  \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\u2502\n\u2502  \u2502 \u2502 Azure Container Registry (Basic SKU)                    \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 - Stores container images for all environments          \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 - Single source of truth for application containers     \u2502 \u2502\u2502\n\u2502  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                   Development Environment                       \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502\n\u2502  \u2502 Resource Group: rg-dev-environment                          \u2502\u2502\n\u2502  \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\u2502\n\u2502  \u2502 \u2502 Container Apps Environment                              \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 Container App (Flask Application)                   \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 - Managed Identity for ACR access                   \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 - Auto-scaling enabled                              \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\u2502\n\u2502  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\u2502\n\u2502  \u2502 Azure Storage Account | Key Vault | Application Insights    \u2502\u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                  Production Environment                         \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502\n\u2502  \u2502 Resource Group: rg-prod-environment                         \u2502\u2502\n\u2502  \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\u2502\n\u2502  \u2502 \u2502 Container Apps Environment (VNET-integrated)            \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 Container App (Same Image as Dev)                   \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 - Enhanced security configuration                   \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2502 - Production-grade scaling rules                    \u2502 \u2502 \u2502\u2502\n\u2502  \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\u2502\n\u2502  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\u2502\n\u2502  \u2502 VNET | Storage | Key Vault | App Insights | Monitoring      \u2502\u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n<h3>Layered infrastructure configuration<\/h3>\n<p>Here\u2019s how the sequence is defined in the <code>azure.yaml<\/code> file:<\/p>\n<pre><code class=\"yaml\"># Azure Container Apps Demo: \"Build Once, Deploy Everywhere\" with Shared ACR\nname: dev-prod\n\n# Layered Infrastructure Deployment Strategy\ninfra:\n  layers:\n    # Layer 1: Foundation - Core infrastructure for each environment\n    - name: foundation\n      path: infra\/foundation\n\n    # Layer 2: Shared ACR - Single registry for all environments  \n    - name: shared-acr\n      path: infra\/shared-acr\n\n    # Layer 3: ACR Role Assignment - Security configuration\n    - name: acr-role\n      path: infra\/acr-role\n\n    # Layer 4: Container App - Application deployment\n    - name: container-app\n      path: infra\/container-app\n\nservices:\n  app:\n    project: .\n    host: containerapp\n    language: python\n<\/code><\/pre>\n<p>This layered approach solves the classic \u201cchicken-and-egg\u201d problem you run into with container deployments. Both dev and prod need to share the same ACR. Your prod Container App needs permissions to pull from ACR, but you can\u2019t assign those permissions until both the Container App identity and the ACR actually exist. By provisioning things in the right sequence, we ensure everything gets the permissions it needs.<\/p>\n<p>Here\u2019s how the layers work:<\/p>\n<ol>\n<li>\n<p><strong>Foundation layer<\/strong>: Sets up core resources based on your <code>AZURE_ENV_TYPE<\/code> \u2013 this includes the Container Apps Environment and Managed Identity<\/p>\n<\/li>\n<li>\n<p><strong>Shared ACR layer<\/strong>: Creates your centralized container registry (unless you already have one)<\/p>\n<\/li>\n<li>\n<p><strong>ACR Role Assignment layer<\/strong>: This is where the magic happens \u2013 gives your Managed Identity the right permissions (dev gets push+pull, prod gets pull-only)<\/p>\n<\/li>\n<li>\n<p><strong>Container App layer<\/strong>: Finally deploys your application, which now has proper ACR access<\/p>\n<\/li>\n<\/ol>\n<p>Each layer outputs the stuff that later layers need \u2013 resource IDs, endpoints, you name it \u2013 and azd automatically pipes those outputs as inputs to the next layer.<\/p>\n<p>For example, if you peek at <code>infra\/acr-role\/main.parameters.json<\/code>, you\u2019ll see how <code>AZURE_CONTAINER_REGISTRY_NAME<\/code> flows from the shared-acr layer into the ACR role assignment layer:<\/p>\n<p><code>\"AZURE_CONTAINER_REGISTRY_NAME\": {<br \/>\n      \"value\": \"${AZURE_CONTAINER_REGISTRY_NAME}\"`<\/code><\/p>\n<h2>Try it out<\/h2>\n<blockquote>\n<p><img data-opt-id=1764954144  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/26a0.png\" alt=\"\u26a0\" class=\"wp-smiley\" \/> <strong>Production Reality Check<\/strong><br \/>\n  While I\u2019m showing you how to deploy locally with <code>azd up<\/code>, <strong>please use CI\/CD pipelines for production deployments<\/strong>. The local workflow I\u2019m demonstrating here is great for rapid prototyping and development, but you\u2019ll want proper CI\/CD controls for anything that matters.<\/p>\n<\/blockquote>\n<h3>Prerequisites<\/h3>\n<ul>\n<li>Azure Developer CLI v1.20.0 or later (<a href=\"https:\/\/aka.ms\/azd-install\">download here<\/a>)<\/li>\n<li>Docker (for local container testing)<\/li>\n<\/ul>\n<h3>1. Clone the Sample Repository<\/h3>\n<pre><code class=\"bash\">azd init -t https:\/\/github.com\/puicchan\/azd-dev-prod-aca-storage\n<\/code><\/pre>\n<h3>2. Set up your development environment<\/h3>\n<p>Development environment setup uses the familiar <code>azd up<\/code> workflow you\u2019re probably already comfortable with:<\/p>\n<pre><code class=\"bash\"># Enable alpha feature for layered infrastructure\nazd config set alpha.layers on\n\n# Create and configure development environment\nazd env new myapp-dev\nazd env set AZURE_ENV_TYPE dev\n\n# Deploy everything: infrastructure + build + push + deploy\nazd up\n<\/code><\/pre>\n<h3>3. Prepare your production infrastructure<\/h3>\n<p>Now you\u2019ll want to set up your production environment infrastructure. This is typically a one-time thing you do before setting up your CI\/CD pipelines:<\/p>\n<pre><code class=\"bash\"># Create production environment\nazd env new myapp-prod\nazd env set AZURE_ENV_TYPE prod\n\n# Reference existing shared ACR (replace with actual values from dev deployment)\nazd env set ACR_RESOURCE_GROUP_NAME rg-shared-acr-resource-group-name\nazd env set AZURE_CONTAINER_REGISTRY_ENDPOINT shared-acr-endpoint\n\n# Provision infrastructure only (no build\/push\/deploy)\nazd provision\n<\/code><\/pre>\n<blockquote>\n<p><img data-opt-id=1764954144  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/26a0.png\" alt=\"\u26a0\" class=\"wp-smiley\" \/> <strong>Critical note about infrastructure<\/strong><br \/>\n  \u2013 I\u2019m using <code>azd provision<\/code> locally here to set up the infrastructure BEFORE going live. <strong>In your CI\/CD pipelines, you should NEVER run <code>azd provision<\/code><\/strong> \u2013 stick to <code>azd deploy<\/code> only. Infrastructure changes in production should go through proper approval processes because accidental modifications can cause outages.<br \/>\n  \u2013 When <code>envType = 'prod'<\/code>, the infrastructure automatically includes VNET integration. For demo purposes (easier testing), I\u2019ve set <code>internal: false<\/code> in <code>aca-environment.bicep<\/code> line 42, so your app stays publicly accessible while the compute is isolated. For truly private environments, you\u2019d flip that to <code>internal: true<\/code> and add a reverse proxy.<\/p>\n<\/blockquote>\n<h3>4. Set up your CI\/CD pipeline<\/h3>\n<p>Now for the fun part \u2013 let\u2019s see the pipeline in action! Make a simple code change and commit it.<\/p>\n<p>For example, modify the <code>&lt;h1&gt;<\/code> tag in <code>index.html<\/code>, then run:<\/p>\n<pre><code class=\"bash\"># Select your dev environment and configure pipeline\nazd env select myapp-dev\n# Make sure you select GitHub as the pipeline provider when prompted\nazd pipeline config\n<\/code><\/pre>\n<p>Here\u2019s what to watch for:<\/p>\n<ol>\n<li><strong>GitHub Actions tab<\/strong>: Head to your repository\u2019s Actions tab<\/li>\n<li><strong>Build stage<\/strong>: Watch the container get built with a unique tag<\/li>\n<li><strong>Dev deployment<\/strong>: See it automatically deploy to development<\/li>\n<li><strong>Same container everywhere<\/strong>: Check both environments \u2013 they\u2019re running the exact same container image<\/li>\n<\/ol>\n<p><img data-opt-id=174120507  data-opt-src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2025\/10\/aca-gh-action.gif\"  class=\"optimole-lazy-only \"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"GitHub Actions workflow in action\" \/><\/p>\n<h2>How the GitHub Actions workflow works<\/h2>\n<p>The workflow follows a clean three-stage pattern: <strong>Build \u2192 Deploy-Dev \u2192 Deploy-Prod<\/strong><\/p>\n<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                        GitHub Actions Workflow                  \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Job 1: BUILD                                                    \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 1. Enable alpha features (layered infrastructure)           \u2502 \u2502\n\u2502 \u2502 2. Set environment names (dev\/prod)                         \u2502 \u2502\n\u2502 \u2502 3. Log in with Azure (Federated Credentials)                \u2502 \u2502\n\u2502 \u2502 4. Provision Infrastructure (dev environment)               \u2502 \u2502\n\u2502 \u2502 5. Build &amp; Publish Container to ACR                         \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 azd publish app                                       \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 Get image: azd env get-value SERVICE_APP_IMAGE_NAME   \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502                                                                 \u2502\n\u2502 Outputs:                                                        \u2502\n\u2502  \u2022 container-image: crXXXX.azurecr.io\/app:azd-deploy-123456     \u2502\n\u2502  \u2022 dev-env-name: myapp-dev                                      \u2502\n\u2502  \u2022 prod-env-name: myapp-prod                                    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                               \u2502\n                               \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Job 2: DEPLOY-DEV                                               \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 1. Enable alpha features (layered infrastructure)           \u2502 \u2502\n\u2502 \u2502 2. Log in with Azure (Federated Credentials)                \u2502 \u2502\n\u2502 \u2502 3. Deploy to Development                                    \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 azd deploy app --from-package &lt;container-image&gt;       \u2502 \u2502\n\u2502 \u2502 4. Validate Application                                     \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 Run validation tests, smoke tests                     \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                               \u2502\n                               \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Job 3: DEPLOY-PROD                                              \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 1. Enable alpha features (layered infrastructure)           \u2502 \u2502\n\u2502 \u2502 2. Log in with Azure (Federated Credentials)                \u2502 \u2502\n\u2502 \u2502 3. Deploy to Production                                     \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 azd deploy app --from-package &lt;same container-image&gt;  \u2502 \u2502\n\u2502 \u2502    \u2514\u2500 Uses shared ACR from environment variables            \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nKey: Same container image (built once) deployed to both environments\n<\/code><\/pre>\n<p>You can see the complete workflow implementation in the <a href=\"https:\/\/github.com\/puicchan\/azd-dev-prod-aca-storage\/blob\/main\/.github\/workflows\/azure-dev.yml\">azure-dev.yml<\/a> file in the repository.<\/p>\n<p><strong>The key things to notice:<\/strong><\/p>\n<ul>\n<li>Container gets built <strong>once<\/strong> in the build stage <\/li>\n<li><code>azd env get-value SERVICE_APP_IMAGE_NAME<\/code> grabs the published image name <\/li>\n<li>Both dev and prod deploy the <strong>exact same container image<\/strong> <\/li>\n<li>Validation steps act as quality gates between stages<\/li>\n<\/ul>\n<blockquote>\n<p><strong>Note:<\/strong> This workflow uses GitHub Actions job outputs to pass the container image name between jobs. That only works on GitHub-hosted runners. If you\u2019re using self-hosted runners, you\u2019ll need a different approach \u2013 maybe store the image name in an artifact or use another method to share data between jobs.<\/p>\n<\/blockquote>\n<h2>Wrapping up<\/h2>\n<p>This post walks through how to implement \u201cbuild once, deploy everywhere\u201d patterns using Azure Container Apps with the new features in Azure Developer CLI v1.20.0. Building on our previous posts about Azure App Service, this Container Apps approach shows how the same core principles work across different Azure compute services.<\/p>\n<p>The combination of layered infrastructure and separated container operations (<code>azd publish<\/code> + <code>azd deploy --from-package<\/code>) gives you a solid foundation when you\u2019re ready to move beyond the simplicity of <code>azd up<\/code> but still want to keep that familiar azd developer experience.<\/p>\n<p><strong>What we covered:<\/strong><\/p>\n<ul>\n<li><strong>Container Apps integration<\/strong>: How azd works with Azure Container Apps out of the box <\/li>\n<li><strong>Layered infrastructure<\/strong>: Sequential deployment with proper dependency management <\/li>\n<li><strong>Environment separation<\/strong>: Keep the dev convenience while adding production-ready controls<\/li>\n<\/ul>\n<p>There is no one way to deploy to production, and even more sophisticated approaches depending on the organization\u2019s needs. Advanced networking, complex compliance requirements, different deployment strategies, etc.; the specific implementation is going to vary based on your team\u2019s situation. We hope this gives you a starting point and an example to follow.<\/p>\n<p>We\u2019re continuing to explore and validate production deployment scenarios with the Azure Developer CLI, making sure azd provides reliable patterns as your applications grow from development to production.<\/p>\n<p>Questions about the 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<p><em>For more Azure Developer CLI content, follow the <a href=\"https:\/\/aka.ms\/azd-blog\/\">Azure Developer CLI blog<\/a> and check out the <a href=\"https:\/\/aka.ms\/azd\">official documentation<\/a>.<\/em><\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/devops\/azure-developer-cli-azure-container-apps-dev-to-prod-deployment-with-layered-infrastructure\/\">Azure Developer CLI: Azure Container Apps Dev-to-Prod Deployment with Layered Infrastructure<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/devops\">Azure DevOps Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>This post walks through how to implement \u201cbuild once, deploy everywhere\u201d patterns using Azure Container Apps with the new azd [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2738,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[3],"tags":[],"class_list":["post-2737","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2737","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=2737"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2737\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/2738"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=2737"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=2737"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=2737"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}