{"id":3557,"date":"2025-12-09T10:34:22","date_gmt":"2025-12-09T18:34:22","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=3557"},"modified":"2025-12-09T10:34:22","modified_gmt":"2025-12-09T18:34:22","slug":"azure-developer-cli-azd-blue-green-aca-deployment","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/azure-developer-cli-azd-blue-green-aca-deployment\/","title":{"rendered":"Blue-green deployment in Azure Container Apps using Azure Developer CLI"},"content":{"rendered":"<h1>Blue-green deployment in Azure Container Apps using Azure Developer CLI<\/h1>\n<p>If you follow the <a href=\"https:\/\/aka.ms\/azd-blog\">Azure Developer CLI blog posts<\/a>, you already know that <strong>Azure Container Apps (ACA) support in Azure Developer CLI (azd) is GA<\/strong>. We want <code>azd<\/code> to be production-ready\u2014you should be able to plug in the Bicep code your team uses and just run <code>azd up<\/code>. While getting the support GA-ready, one of the improvements we made was <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/5694\">revision-based deployment support<\/a>, which means <code>azd<\/code> can now handle advanced rollout patterns like <strong>blue-green deployment<\/strong>.<\/p>\n<p>Blue-green deployment brings back fond memories. One of the first Jenkins blog posts I worked on was about blue-green deployment for Azure Kubernetes Service (AKS). I remember how much effort the engineer put into creating that quickstart\u2014and how much longer I spent trying to understand what was going on. It&#8217;s great to see something that complex become almost trivial now.<\/p>\n<p>For this demo, I asked Copilot to help generate a Python app to illustrate the workflow. No code changes needed\u2014you can clearly see whether you&#8217;re using the blue or green revision. The Bicep is based on <a href=\"https:\/\/learn.microsoft.com\/azure\/container-apps\/blue-green-deployment?pivots=bicep\">Blue-green deployment in ACA<\/a> and the workflow on the <a href=\"https:\/\/github.com\/Azure-Samples\/containerapps-blue-green\">containerapps-blue-green sample<\/a>.<\/p>\n<h2>To run locally<\/h2>\n<blockquote><p><strong>Note:<\/strong> The commit IDs in this demo (<code>fb699ef<\/code>, <code>c6f1515<\/code>) are just examples. You can use any identifiers you want\u2014like <code>blue-v1<\/code>, <code>12345<\/code>, or actual git commit hashes (as shown in the CI\/CD workflow <code>azure-dev.yml<\/code>).<\/p><\/blockquote>\n<ol>\n<li>In an empty folder, run <code>azd init -t puichan\/aca-blue-green<\/code>.<\/li>\n<li>To deploy BLUE:\n<pre><code>azd env set BLUE_COMMIT_ID fb699ef\r\nazd env set LATEST_COMMIT_ID fb699ef<\/code><\/pre>\n<\/li>\n<li>Run <code>azd up<\/code>\n<blockquote><p><strong>Note:<\/strong> On first deployment, only the blue revision is created since <code>GREEN_COMMIT_ID<\/code> isn&#8217;t set. The Bicep logic (<code>!empty(blueCommitId) &amp;&amp; !empty(greenCommitId)<\/code>) skips traffic splitting until both revisions exist.<\/p><\/blockquote>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2025\/12\/12-03-azd-blue-green-deployment-aca-blue.webp\" alt=\"Blue deployment showing ACA blue revision\" \/><\/li>\n<li>To deploy GREEN, but keep traffic on BLUE:\n<pre><code>\/\/ Create green revision\r\nazd env set GREEN_COMMIT_ID c6f1515\r\nazd env set LATEST_COMMIT_ID c6f1515\r\n\r\n\/\/ keep traffic on BLUE\r\nazd env set PRODUCTION_LABEL blue<\/code><\/pre>\n<blockquote><p><strong>Note:<\/strong> <code>LATEST_COMMIT_ID<\/code> tells Bicep which revision to deploy. When you change it, a new revision is created. When you keep it the same and only change <code>PRODUCTION_LABEL<\/code> (as shown later), traffic just switches between existing revisions.<\/p><\/blockquote>\n<p>And then run <code>azd deploy<\/code><\/p>\n<p>If you click the app endpoint, you still see the blue deployment. You see the blue deployment because <code>PRODUCTION_LABEL<\/code> is set to &#8220;blue&#8221;\u2014traffic stays on blue. You can see the traffic logic in <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/infra\/web.bicep\"><code>web.bicep<\/code><\/a>:<\/p>\n<pre><code class=\"language-Bicep\">      traffic: !empty(blueCommitId) &amp;&amp; !empty(greenCommitId) ? [\r\n      {\r\n        revisionName: '${containerAppName}--${blueCommitId}'\r\n        weight: productionLabel == 'blue' ? 100 : 0\r\n        label: 'blue'\r\n      }\r\n      {\r\n        revisionName: '${containerAppName}--${greenCommitId}'\r\n        weight: productionLabel == 'green' ? 100 : 0\r\n        label: 'green'\r\n      }<\/code><\/pre>\n<p>To test, run this command to get all three fully qualified domain names (FQDNs):<\/p>\n<pre><code class=\"language-powershell\">$uri = azd env get-value \"SERVICE_WEB_URI\"; $domain = ([System.Uri]$uri).Host.Split('.', 2)[1]; Write-Host \"Production FQDN: $uri\"; Write-Host \"Blue label FQDN: https:\/\/web---blue.$domain\"; Write-Host \"Green label FQDN: https:\/\/web---green.$domain\"<\/code><\/pre>\n<p>Click the links in the output to see the production, blue and green label FQDN.<\/p>\n<pre><code>Production FQDN: https:\/\/web.purplerock-d77264b4.westus.azurecontainerapps.io\r\nBlue label FQDN: https:\/\/web---blue.purplerock-d77264b4.westus.azurecontainerapps.io\r\nGreen label FQDN: https:\/\/web---green.purplerock-d77264b4.westus.azurecontainerapps.io<\/code><\/pre>\n<p>You can also run <code>az containerapp ingress traffic show --name web --resource-group &lt;your-resource-group&gt; -o table<\/code>. Output:<\/p>\n<pre><code>Label    RevisionName    Weight\r\n-------  --------------  --------\r\nblue     web--fb699ef    100\r\ngreen    web--c6f1515    0<\/code><\/pre>\n<\/li>\n<li>To send production traffic to the green revision, set <code>PRODUCTION_LABEL<\/code> to &#8220;green&#8221; and then run <code>azd deploy<\/code> again:\n<pre><code>azd env set PRODUCTION_LABEL green\r\nazd deploy<\/code><\/pre>\n<p>Click the endpoint\u2014ta-da, green!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2025\/12\/12-03-azd-blue-green-deployment-aca-green.webp\" alt=\"Green deployment showing ACA green revision\" \/><\/p>\n<p>Again, you can check:<\/p>\n<pre><code>az containerapp ingress traffic show --name web --resource-group &lt;your-resource-group&gt; -o table<\/code><\/pre>\n<\/li>\n<li>To roll back to BLUE:\n<pre><code>azd env set PRODUCTION_LABEL blue\r\nazd deploy<\/code><\/pre>\n<\/li>\n<\/ol>\n<h2>CI\/CD with GitHub Actions<\/h2>\n<p>To set up the GitHub workflow, the <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\">sample repo<\/a> contains a customized <code>azure-dev.yml<\/code>. Run the <code>azd pipeline config<\/code> command and you&#8217;re done.<\/p>\n<h2>Things to know<\/h2>\n<ul>\n<li>In revision-based strategy, <code>azd deploy<\/code> deploys both the container app definition and image together based on <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/infra\/web.bicep\"><code>web.bicep<\/code><\/a>. The revision-based approach means changes to environment variables, images, resources, and load-balancing settings all roll out as a single revision. For more information, see the <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/container-apps-workflows?tabs=avm-module#revision-based-deployment-strategy\">official doc<\/a>.<\/li>\n<li>I override the default Docker image name and tag in <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/azure.yaml\"><code>azure.yaml<\/code><\/a> to specify my custom repository name and image instead of using azd&#8217;s defaults. Environment variables flow to Bicep through <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/infra\/web.parameters.json\"><code>web.parameters.json<\/code><\/a>\u2014when you set <code>BLUE_COMMIT_ID<\/code> via <code>azd env set<\/code>, it maps to the <code>blueCommitId<\/code> Bicep parameter automatically. The environment variable mapping is why both manual commands and the GitHub workflow can control the same deployment logic.\n<pre><code class=\"language-yaml\"># Define the services\r\nservices:\r\nweb:\r\n  project: .\r\n  language: python\r\n  host: containerapp\r\n  docker:\r\n    image: aca-blue-green\/svc \r\n    tag: web-${LATEST_COMMIT_ID}<\/code><\/pre>\n<\/li>\n<li>The <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/.github\/workflows\/azure-dev.yml\">GitHub workflow<\/a> (<code>azure-dev.yml<\/code>) automates the blue-green deployment:\n<ul>\n<li>It uses the current commit ID as part of the revision label:\n<pre><code class=\"language-yaml\">- name: Get current commit ID\r\nid: commit\r\nrun: echo \"commit_id=${GITHUB_SHA::7}\" &gt;&gt; $GITHUB_OUTPUT<\/code><\/pre>\n<\/li>\n<li>It handles blue-green rotation automatically: checks whether blue or green is currently production, deploys the opposite color as the new version, and switches traffic over once everything looks good. Each deployment alternates between blue and green.<\/li>\n<li>It uses ACA tags to remember state between deployments. It reads the tags to figure out which revision is currently production, then deploys to the opposite color. The tag-based state management is how it alternates automatically without hardcoding anything.<\/li>\n<\/ul>\n<\/li>\n<li>ACA automatically cleans up old revisions. The configuration sets <code>maxInactiveRevisions: 10<\/code> in <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\/blob\/main\/infra\/web.bicep\"><code>web.bicep<\/code><\/a>, so Azure keeps up to 10 inactive revisions before removing the oldest ones. You can also explicitly deactivate revisions manually if needed\u2014see the <a href=\"https:\/\/github.com\/Azure-Samples\/containerapps-blue-green\">containerapps-blue-green sample<\/a> for examples.<\/li>\n<\/ul>\n<h2>Wrapping Up<\/h2>\n<p>Working through this blue-green deployment with <code>azd<\/code> shows how much simpler things are now. What used to take pages of Jenkins pipeline configuration now fits in a few environment variables and Bicep files. The real learning here is understanding that ACA revisions are <strong>immutable<\/strong>\u2014you can&#8217;t change them once created. This immutability makes the blue-green pattern cleaner because each deployment creates a fresh revision, you point a label at it, and manage traffic weights. No confusion about which version is running.<\/p>\n<p>The <code>azd<\/code> revision-based deployment support gives you production-grade control without losing the simplicity of <code>azd up<\/code>. You get to keep your team&#8217;s Bicep patterns while adding sophisticated rollout strategies on top.<\/p>\n<p>If you try out ACA revision-based deployment, we&#8217;d love to hear how it goes. Use <a href=\"https:\/\/aka.ms\/azd-user-research-signup\">this form<\/a> to share your feedback. The <a href=\"https:\/\/github.com\/puicchan\/aca-blue-green\">full sample is on GitHub<\/a> if you want to poke around. And if something breaks or you have questions, drop a comment. I&#8217;m probably still figuring out things too.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to implement blue-green deployment in Azure Container Apps using Azure Developer CLI (azd) revision-based deployment strategy.<\/p>\n","protected":false},"author":111321,"featured_media":3559,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[946,864,939,162],"class_list":["post-3557","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-azure-container-apps","tag-azure-developer-cli","tag-docker","tag-python"],"acf":[],"blog_post_summary":"<p>Learn how to implement blue-green deployment in Azure Container Apps using Azure Developer CLI (azd) revision-based deployment strategy.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/3557","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/111321"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=3557"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/3557\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/3559"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=3557"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=3557"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=3557"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}