<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>App Service on Andrew Wilson's Blog</title><link>https://andrewilson.co.uk/tags/app-service/</link><description>Recent content in App Service on Andrew Wilson's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 02 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://andrewilson.co.uk/tags/app-service/index.xml" rel="self" type="application/rss+xml"/><item><title>Automating web.config Environment Transforms in Azure DevOps Pipelines for App Services</title><link>https://andrewilson.co.uk/post/2025/07/webconfig-environment-transform-azure-devops-app-service/</link><pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/07/webconfig-environment-transform-azure-devops-app-service/</guid><description>Background Modernising legacy applications is always a journey, and recently I tackled moving an existing WCF web service into Azure App Service. If you’ve worked with WCF, you’ll know the web.config file is the nerve centre, handling everything from parameters to connection strings.
But here’s the catch, this service needs to run in multiple environments (Development, Test, and Production) each with its own unique settings. That means multiple config files:</description><content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Modernising legacy applications is always a journey, and recently I tackled moving an existing WCF web service into Azure App Service. If you’ve worked with WCF, you’ll know the <code>web.config</code> file is the nerve centre, handling everything from parameters to connection strings.</p>
<p>But here’s the catch, this service needs to run in multiple environments (Development, Test, and Production) each with its own unique settings. That means multiple config files:</p>
<ul>
<li>Web.Dev.Config</li>
<li>Web.Test.Config</li>
<li>Web.Production.Config</li>
</ul>
<h2 id="the-challenge">The challenge</h2>
<p>I didn’t want to build and maintain three separate web packages, one for each environment. That’s a recipe for drift and deployment headaches. Instead, I wanted a single build artifact that could be promoted through environments, swapping out only the environment-specific config as needed.</p>
<p>Enter <a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/transform-webconfig?view=aspnetcore-9.0">web.config transformations</a>. With these, you can:</p>
<ul>
<li>Keep a base <code>web.config</code></li>
<li>Layer on environment-specific transforms (<code>Web.Dev.Config</code>, etc.)</li>
</ul>
<p>But when should the transformation happen? There are two main options, each with its own pros and cons:</p>
<ol>
<li>
<p><strong>At build time (using MSBuild):</strong></p>
<ul>
<li>The transformation is applied as part of the build process, so the output artifact already contains the environment-specific configuration.</li>
<li>This approach is simple if you only ever deploy to one environment per build, but it means you need to build a separate artifact for each environment. This breaks the principle of binary promotion, where the same artifact is promoted through Dev, Test, and Production. It also increases the risk of inconsistencies between builds.</li>
</ul>
</li>
<li>
<p><strong>At deployment (using the AzureRMWebAppDeployment@4 Azure DevOps Task):</strong></p>
<ul>
<li>The transformation is applied as part of the deployment process, allowing you to use a single, environment-agnostic build artifact and inject the correct configuration at deploy time.</li>
<li>This is the preferred approach for most modern pipelines, as it enables true binary promotion, reduces build times, and ensures that what you test is exactly what you deploy to production. It also aligns with best practices for repeatable, reliable deployments.</li>
</ul>
</li>
</ol>
<h2 id="solution-deploy-time-transforms">Solution: deploy-time transforms</h2>
<p>Here’s how I set it up:</p>
<h3 id="build-pipeline">Build pipeline</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">stages</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Build_WCFService&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">checkout</span>: self
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchDepth</span>: <span style="color:#bd93f9">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: VSBuild@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Build the WCF Service Solution&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">solution</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\*.sln&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">msbuildArgs</span>: <span style="color:#f1fa8c">&#39;/p:OutputPath=$(Build.ArtifactStagingDirectory)\WCFService /p:IsTransformWebConfigDisabled=true&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">platform</span>: <span style="color:#f1fa8c">&#39;any cpu&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">vsVersion</span>: <span style="color:#f1fa8c">&#39;latest&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">configuration</span>: <span style="color:#f1fa8c">&#39;Release&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">clean</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: ArchiveFiles@2
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Archive WCF Service Build Output&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">rootFolderOrFile</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\_PublishedWebsites\WCFService.Web&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">includeRootFolder</span>: <span style="color:#ff79c6">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">archiveType</span>: <span style="color:#f1fa8c">&#39;zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">archiveFile</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">replaceExistingArchive</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish WCF Service Build Artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;WCFService&#39;</span>
</span></span></code></pre></div><h3 id="release-pipeline">Release pipeline</h3>
<p>Each environment (Dev, Test, Production) will get its own stage. The key is to ensure the correct environment name is set so the right transform is applied.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Dev
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceDev
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Test Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Test
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceTest
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Prod&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceProd
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span></code></pre></div><h3 id="deployment-template">Deployment template</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4">#YAML</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download the wcf Service Web package&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: WCFService
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureRMWebAppDeployment@4
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy WCF Service Web App&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">ConnectionType</span>: <span style="color:#f1fa8c">&#39;AzureRM&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">WebAppName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.AppServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">Package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/WCFService/WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">enableXmlTransform</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></div><p>Here’s the bit that tripped me up: in multi-stage pipelines, the <code>Release.EnvironmentName</code> variable isn’t set by default. Without it, Azure DevOps doesn’t know which transform to apply. The fix? Explicitly set the variable in each stage:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Test Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span></code></pre></div><h2 id="troubleshooting-and-tips">Troubleshooting and tips</h2>
<p><strong>Common pitfalls:</strong></p>
<ul>
<li>Ensure your transform files (e.g., <code>Web.Dev.config</code>) are included in your build artifacts. If they’re missing, check your <code>.csproj</code> or project file to confirm they’re marked as <code>Content</code> and set to <code>Copy to Output Directory</code> if needed.</li>
<li>File names are case-sensitive on some systems, double-check spelling and casing.</li>
<li>If transforms aren’t being applied, verify that the <code>Release.EnvironmentName</code> variable is set correctly and matches your transform file naming.</li>
</ul>
<p><strong>Security note:</strong>
Avoid storing secrets or sensitive values directly in your config files. Use Azure Key Vault or pipeline secrets for sensitive data, and reference them via environment variables or pipeline variables where possible.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>With this approach, you get a single, reusable build artifact and environment-specific configuration at deployment, no more juggling multiple packages. It’s a small change that makes your pipeline cleaner and your deployments more reliable.</p>
<p>Hope this helps, happy deploying!</p>
]]></content:encoded></item><item><title>How to Set Up Manual Approval for Azure App Service Slot Swaps in Azure DevOps Pipelines</title><link>https://andrewilson.co.uk/post/2025/06/manual-approval-azure-app-service-slot-swap-azure-devops/</link><pubDate>Fri, 06 Jun 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/06/manual-approval-azure-app-service-slot-swap-azure-devops/</guid><description>Overview Deploying updates to production environments demands both speed and control. Azure App Service deployment slots, combined with Azure DevOps pipelines, offer a powerful way to manage releases, enabling teams to validate changes in a live-like environment before they go public. However, ensuring that only reviewed and approved changes reach your users is critical for maintaining reliability and compliance.
Azure App Service Slots unlock powerful advantages:
Site Review. Preview and test your site before it goes live, ensuring everything works as expected.</description><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Deploying updates to production environments demands both speed and control. Azure App Service deployment slots, combined with Azure DevOps pipelines, offer a powerful way to manage releases, enabling teams to validate changes in a live-like environment before they go public. However, ensuring that only reviewed and approved changes reach your users is critical for maintaining reliability and compliance.</p>
<p>Azure App Service Slots unlock powerful advantages:</p>
<ul>
<li><strong>Site Review</strong>. Preview and test your site before it goes live, ensuring everything works as expected.</li>
<li><strong>Warmed-up Instances</strong>. Swap with confidence. Your instances are pre-warmed, so there’s no downtime, seamless traffic redirection, and zero dropped requests.</li>
<li><strong>Instant Rollback</strong>. If something goes wrong after a swap, simply swap back to restore your last known good version in seconds.</li>
</ul>
<p>Best of all, deployment slots come at no extra cost. Check the <a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-app-service-limits">Azure App Service limits</a> to see how many slots your App Service Tier supports.</p>
<h2 id="automatic-vs-manual-slot-swaps">Automatic vs. Manual Slot Swaps</h2>
<p>When it comes to swapping slots, there are two main approaches: <strong>automatic</strong> and <strong>manual</strong>.</p>
<ul>
<li><strong>Automatic swaps</strong> are ideal when you want to quickly promote changes without a review, while still benefiting from pre-warmed instances and instant rollback.</li>
<li><strong>Manual swaps</strong> give you the opportunity to review and verify your site before it goes live, adding an extra layer of confidence.</li>
</ul>
<p>But how do you keep a manual swap process governed and controlled? That’s where Azure DevOps pipelines and approvals come in.</p>
<p>In this article, you’ll learn how to set up a robust, approval-driven slot swap process in Azure DevOps.</p>
<h2 id="defining-azure-resources-with-bicep">Defining Azure Resources with Bicep</h2>
<p>First, we need to define the resources needed in Azure. To do this I am going to use Infrastructure as Code (IaC).
The template shown below creates the following resources:</p>
<ul>
<li>App Service Plan.</li>
<li>App Service linked to the App Service Plan
<ul>
<li>Sub Resource that defines the App Service App Settings</li>
</ul>
</li>
<li>App Service Deployment Slot. <em>Only created for Production environments - this is enforced through a conditional on the resource</em></li>
</ul>
<h3 id="bicep-template-app-service-and-deployment-slot">Bicep Template: App Service and Deployment Slot</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Bicep" data-lang="Bicep"><span style="display:flex;"><span><span style="color:#6272a4">// Bicep</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">/**********************************</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Bicep</span> <span style="color:#8be9fd;font-style:italic">Template</span>: <span style="color:#8be9fd;font-style:italic">App</span> <span style="color:#8be9fd;font-style:italic">Service</span> <span style="color:#8be9fd;font-style:italic">Deploy</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Author</span>: <span style="color:#8be9fd;font-style:italic">Andrew</span> <span style="color:#8be9fd;font-style:italic">Wilson</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">***********************************/</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">targetScope</span> = <span style="color:#f1fa8c">&#39;resourceGroup&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Parameters **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ****************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Org Project Name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">projectName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Application Name sitting within the project&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">applicationName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;location name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">location</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#50fa7b">resourceGroup</span>().<span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Environment name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">env</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;dev&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;App Service Plan sku name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">appServicePlanSkuName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;App Service Plan sku tier&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">appServicePlanSkuTier</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Name of the deployment slot&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">deploymentSlotName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;staging&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Variables **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ***************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> <span style="color:#8be9fd;font-style:italic">appServicePlanName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;asp-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">applicationName</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">env</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">location</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> <span style="color:#8be9fd;font-style:italic">appServiceName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;app-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">applicationName</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">env</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">location</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Resources **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ***************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy an Azure App Service Plan&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppServicePlan</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/serverfarms@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">sku</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanSkuName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">tier</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanSkuTier</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy an Azure App Service&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppService</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/sites@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServiceName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">properties</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">serverFarmId</span>: <span style="color:#8be9fd;font-style:italic">AppServicePlan</span>.<span style="color:#8be9fd;font-style:italic">id</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">httpsOnly</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppServiceAppSettings</span> <span style="color:#f1fa8c">&#39;config@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#f1fa8c">&#39;appsettings&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">properties</span>: {
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }  
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy a deployment slot for the App Service&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">deploymentSlot</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/sites/slots@2024-11-01&#39;</span> = <span style="color:#8be9fd;font-style:italic">if</span> (<span style="color:#8be9fd;font-style:italic">env</span> <span style="color:#ff79c6">==</span> <span style="color:#f1fa8c">&#39;prod&#39;</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">deploymentSlotName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">parent</span>: <span style="color:#8be9fd;font-style:italic">AppService</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">properties</span>: { }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Outputs **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// *************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Outputs the App Service Name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">output</span> <span style="color:#8be9fd;font-style:italic">appServiceName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#8be9fd;font-style:italic">appServiceName</span>
</span></span></code></pre></div><h3 id="important-note-avoiding-autoswap-for-manual-swaps">Important Note: Avoiding AutoSwap for Manual Swaps</h3>
<p>Make sure that the DeploymentSlot resource does not have AutoSwap setup as this will counteract when Manual Swap is used. Such that the following process would be observed:</p>
<ol>
<li>Deploy to Staging slot.</li>
<li>AutoSwap kicks in and swaps Staging with Production.</li>
<li>After Manual Approval, Slots are swapped again</li>
<li>You are now in the originating position with the new site still in Staging and Production with the old.</li>
</ol>
<h2 id="designing-the-azure-devops-pipeline">Designing the Azure DevOps Pipeline</h2>
<p>Next we will setup our Azure DevOps Yaml pipeline. The pipeline’s goal is to orchestrate artifact building and environment deployments while adhering to DevOps best practices, such as <strong>binary promotion</strong>.</p>
<h3 id="pipeline-stages-explained">Pipeline Stages Explained</h3>
<p>The pipeline has the following stages:</p>
<ol>
<li>
<p><strong>Build_Packages</strong>. Build a single set of artifacts that have been tested and versioned. (<em>Best practice of Binary Promotion</em>)</p>
<p>The stage has two jobs to handle both Azure Bicep Templates and the App Service Web Package.</p>
</li>
<li>
<p><strong>Development</strong>. Deploy to the Development Environment using an environment agnostic pipeline template*.</p>
</li>
<li>
<p><strong>Staging</strong>. Deploy to the Staging Environment using an environment agnostic pipeline template*.</p>
</li>
<li>
<p><strong>Production</strong>. Deploy to the Production Environment using an environment agnostic pipeline template*.</p>
</li>
</ol>
<p>*The environment agnostic pipeline template is a reusable template for all environments.</p>
<h3 id="pipeline-yaml-build-and-environment-deployments">Pipeline YAML: Build and Environment Deployments</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Orchestrating Template: Build And Environment Deployments</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">stages</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Test_Build_And_Version_Bicep_Templates&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish ARMOutput as template build artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/templates&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;ARMtemplates&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Build_and_Version_AppService&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish App Service Build Artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/AppService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;AppService&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Development&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Dev
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Staging&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Staging Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Staging
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span></code></pre></div><p>Once the Build_Packages stage has completed successfully, we now have two pipeline artifacts, ARMtemplates and AppService. In each environment deploy these two artifacts will be downloaded and used alongside environment specific Library Variable Groups to create specific environment deployments.</p>
<h3 id="environment-agnostic-deployment-template">Environment-Agnostic Deployment Template</h3>
<p>The environment agnostic template is shown below and has the following steps:</p>
<ol>
<li><strong>Artifact Downloads</strong>. Downloads the two pipeline artifacts, ARMtemplates and AppService.</li>
<li><strong>Azure Resource Group Deployment</strong>. Using the built ARM template, specific environment library variable group and ARM Connection.</li>
<li><strong>Custom PowerShell Script to Obtain Azure Deployment Outputs</strong>. Deployment output such as appServiceName will be created as job variables ready for future tasks to access.</li>
<li><strong><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-web-app-v1?view=azure-pipelines">Azure Web App Deploy</a> (Specific for Dev and Staging)</strong>. This task deploys straight to the production slot for non production environments.</li>
<li><strong><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-web-app-v1?view=azure-pipelines">Azure Web App Deploy</a> (Specific for Prod)</strong>. This task deploys to the staging slot ready for a manual slot swap.</li>
</ol>
<h3 id="pipeline-yaml-template-reusable-environment-agnostic-deployments">Pipeline YAML Template: Reusable Environment-Agnostic Deployments</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Environment Deploy Template: Deploy Azure Resources and Obtain Outputs, Deploy App Service</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download ARM templates&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: ARMtemplates
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download the App Service Web package&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: AppService
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureResourceManagerTemplateDeployment@3
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service ARM template&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentScope</span>: <span style="color:#f1fa8c">&#39;Resource Group&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureResourceManagerConnection</span>: ${{ parameters.ARMConn}}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">subscriptionId</span>: <span style="color:#f1fa8c">&#39;$(subscriptionId)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">action</span>: <span style="color:#f1fa8c">&#39;Create Or Update Resource Group&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">resourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">location</span>: <span style="color:#f1fa8c">&#39;$(location)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">templateLocation</span>: <span style="color:#f1fa8c">&#39;Linked artifact&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">csmFile</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/ARMtemplates/AppService.azuredeploy.json&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentMode</span>: <span style="color:#f1fa8c">&#39;Incremental&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentOutputs</span>: deploymentOutputs
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">overrideParameters</span>: &gt;-<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        ...</span>        
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: PowerShell@2
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">name</span>: armOutput
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Obtain Azure Deployment outputs&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">targetType</span>: <span style="color:#f1fa8c">&#39;inline&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">script</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        if (![string]::IsNullOrEmpty( &#39;$(deploymentOutputs)&#39; )) {
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          $DeploymentOutputs = convertfrom-json &#39;$(deploymentOutputs)&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          $DeploymentOutputs.PSObject.Properties | ForEach-Object {
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              $keyname = $_.Name
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              $value = $_.Value.value
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              Write-Host &#34;The value of [$keyName] is [$value]&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              Write-Host &#34;##vso[task.setvariable variable=$keyname;isOutput=true]$value&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          }
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        }</span>        
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureWebApp@1
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service - Dev and Staging Environment Only&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">condition</span>: ne(variables[&#39;env&#39;], &#39;prod&#39;)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/AppService/AppService.zip&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureWebApp@1
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service to Staging Slot - Prod Environment Only&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">condition</span>: eq(variables[&#39;env&#39;], &#39;prod&#39;)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deployToSlotOrASE</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">resourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">slotName</span>: <span style="color:#f1fa8c">&#39;$(slotName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/AppService/AppService.zip&#39;</span>
</span></span></code></pre></div><h3 id="accessing-arm-output-variables-across-jobs">Accessing ARM Output Variables Across Jobs</h3>
<p>Given that I will also need to access the appServiceName ARM Output that has been outputted from the armOutput task as a variable in future jobs, I have used the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&amp;tabs=bash#set-an-output-variable-for-use-in-future-jobs"><code>Output Variable for use in future jobs</code></a> configuration.</p>
<p>Rather than job only:</p>
<blockquote>
<p>##vso[task.setvariable variable=$keyname]$value</p>
</blockquote>
<p>It uses the IsOutput so we can setup dependencies and access later:</p>
<blockquote>
<p>##vso[task.setvariable variable=$keyname;<code>isOutput=true</code>]$value</p>
</blockquote>
<p><strong>Note</strong>: Accessing a Output Variable is different to a non-output variable:</p>
<blockquote>
<p>Non-output Variable: <code>'$(appServiceName)'</code></p>
<p>Output Variable: <code>'$(armOutput.appServiceName)'</code>, you will need to reference the task name prior to the variable.</p>
</blockquote>
<h3 id="manual-slot-swap-with-azure-devops-approvals">Manual Slot Swap with Azure DevOps Approvals</h3>
<p>At this point, we now have our App Service resources deployed and the Site deployed to the production slots for Dev and Staging, and deployed to the Staging slot for Production.</p>
<p>But now down to the real question, how do I manually swap the slots using some Azure DevOps governance goodness?</p>
<p>To do this we are going to use the Azure DevOps Environment Approvals which will allow specific groups or users to approve the slot swap. Following this we will use the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-app-service-manage-v0?view=azure-pipelines">AzureAppServiceManage@0</a> task to conduct the slot swap.</p>
<h3 id="setting-up-azure-devops-environments-and-approvals">Setting Up Azure DevOps Environments and Approvals</h3>
<p>To setup an environment and approvals:</p>
<ol>
<li>Sign in to your Azure DevOps organisation and open your project.</li>
<li>Select <strong>Pipelines</strong> &gt; <strong>Environments</strong> &gt; <strong>Create Environment</strong>.</li>
<li>Enter information for the environment, and then select <strong>Create</strong>. (For our purposes leave Resource as None)</li>
<li>In your Environment, Select <strong>Approvals and checks</strong> tab, and then select the + sign to add a new check.</li>
<li>Select <strong>Approvals</strong>, and then select <strong>Next</strong>.</li>
<li>Add users or groups as your designated <strong>Approvers</strong>, and if desired the following
<ol>
<li>Instructions to approvers</li>
<li>Allow approvers to approve their own runs</li>
</ol>
</li>
<li>Make sure to Save.</li>
</ol>
<p>Environments can be assigned to stages and jobs, but not to individual tasks. This means the slot swap task cannot reside within the environment-agnostic deployment template; it must be a separate, follow-on job.</p>
<p>To cater for the slot swap and environment approval, we are going to amend the orchestrating template to add another Job after the deployment Job in the Production deploy stage. This is shown below.</p>
<h3 id="adding-the-slot-swap-job-to-the-pipeline">Adding the Slot Swap Job to the Pipeline</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Orchestrating Template: Build And Environment Deployments</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Slot_Swap_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">dependsOn</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: ProductionEnv
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">appServiceName</span>: $[ dependencies.Deploy_to_Production.outputs[&#39;Deploy_to_Production.armOutput.appServiceName&#39;] ]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">task</span>: AzureAppServiceManage@0
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Swap App Service Staging Slot to Production&#39;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">azureSubscription</span>: <span style="color:#f1fa8c">&#39;XXXYYYZZZ&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">action</span>: <span style="color:#f1fa8c">&#39;Swap Slots&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">WebAppName</span>: <span style="color:#f1fa8c">&#39;$(appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ResourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">SourceSlot</span>: <span style="color:#f1fa8c">&#39;$(slotName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">SwapWithProduction</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></div><p>As can be seen in the amended template above, the Slot_Swap_to_Production job runs after the Deploy_to_Production job and expects it to have succeeded as a condition to running. The Slot_Swap_to_Production job also references the environment ProductionEnv for Approval prior to running.</p>
<p><strong>Note</strong>: The first time the environment is used on the pipeline, the pipeline will ask for permission to use the resource.</p>
<h3 id="referencing-output-variables-in-the-slot-swap-job">Referencing Output Variables in the Slot Swap Job</h3>
<p>The Azure App Service Manage task requires the <code>WebAppName</code>, which is set as an output variable in the environment-agnostic YAML pipeline template. To use this value in the <code>Slot_Swap_to_Production</code> job, define a new variable that references the output variable from the previous job. Once defined, you can use it within the current job as a standard variable (e.g., <code>$(appServiceName)</code>).</p>
<p>The variable reference uses the following syntax:</p>
<blockquote>
<p>[dependencies.<code>JobName</code>.outputs[&rsquo;<code>JobName</code>.<code>TaskName</code>.<code>VariableName</code>]]</p>
</blockquote>
<p>You now have a pipeline that at the point of the slot swap job will wait for an appropriate approval prior to running and swapping your staging slot to production.</p>
<h2 id="summary">Summary</h2>
<p>By combining Azure App Service deployment slots, Infrastructure as Code, and Azure DevOps environment approvals, you can create a robust, auditable deployment pipeline. This approach ensures that production releases are gated by manual approval, reducing risk and enabling rapid rollback if needed.</p>
<p>Ready to take your deployments to the next level? Try setting up manual slot swaps with approvals in your next release pipeline, and let your team experience stress-free, governed production launches.</p>
]]></content:encoded></item></channel></rss>