So far we have a serious pipeline: build once, promote through environments with approval gates, and infrastructure defined as code with Bicep. There remains an unpleasant moment – the very one when the application reaches the customers.
When you deploy directly over production, the App Service copies the new files and restarts the application. And between "it restarted" and "it is ready to respond" there are a few good seconds during which users can receive errors.
A deploy that takes your site down for ten seconds at every release is not a professional deploy – it is a planned outage.
🎯 Why a direct deploy causes downtime
Even if everything is automated, a direct deploy over the production instance means:
-
Files are overwritten – while copying, the application is in an inconsistent state.
-
The application restarts – the old process stops, the new one starts.
-
Cold start – the first request finds a cold app: JIT, database connections, empty cache. Responses are slow or fail.
For a site with real traffic, this means visible errors at every release. Azure's solution for this problem is called deployment slots.
🔀 What are deployment slots
A deployment slot is a live, separate instance of the same App Service. Besides the main slot (production), you create another one (staging), with its own hostname, but running on the same App Service Plan.
The basic idea:
-
You deploy the new version in the staging slot, not directly in production.
-
You let it "warm up" – it starts, connects to the database, fills its cache.
-
You check that it is healthy on the staging hostname.
-
You do a swap – production instantly points to the already warm instance.
You don't deploy to production. You deploy next to production, warm up, then switch.
An important note: slots are available from the Standard tier upwards (S1, P1v3, etc.). On Basic or Free you don't have slots – that's why, in the Bicep from part 3, production was on a Premium plan.
⚡ Why the swap gives zero real downtime
Here is the key. The swap does not copy files and does not restart anything at the moment of switching. It is just a routing change – traffic that was going to the old instance is redirected to the new one, which is already running and warm.
The difference compared to a direct deploy:
-
Direct deploy: restart + cold start on the production instance, with users on it.
-
Slot swap: the new instance is already warm before the swap; switching is almost instantaneous, without visible cold start.
🔥 Warm-up and sticky settings
Before completing the swap, Azure can send requests to the new slot to fully start it – this is the warm-up (applicationInitialization). Only after the slot responds does the swap complete.
The second important concept is sticky settings. Normally, at swap, the app settings move together with the code. But some settings must remain attached to the slot – for example, you want the staging slot to always use the staging connection string, not to carry it into production at swap.
In Azure, you mark an app setting or connection string as a "deployment slot setting" – then it stays fixed on its slot and does not move at swap.
🧱 The slot, defined in Bicep
Being infrastructure as well, the slot is cleanly defined in Bicep as a child resource of the web app (referring to part 3):
resource stagingSlot 'Microsoft.Web/sites/slots@2023-12-01' = {
parent: webApp
name: 'staging'
location: location
properties: {
serverFarmId: plan.id
httpsOnly: true
siteConfig: {
netFrameworkVersion: 'v8.0'
alwaysOn: true
}
}
}
parent: webApp makes staging a slot of the main application. alwaysOn: true keeps it running so that warm-up is fast.
🔗 In the pipeline: deploy in slot, then swap
The production job now does two steps – deploys in staging, then switches:
deploy-production:
runs-on: ubuntu-latest
needs: build-test
environment:
name: production
steps:
- uses: actions/download-artifact@v4
with:
name: app
path: ./publish
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy in staging slot
uses: azure/webapps-deploy@v3
with:
app-name: my-app-prod
slot-name: staging
package: ./publish
- name: Swap staging into production
run: |
az webapp deployment slot swap \
--resource-group rg-myapp-prod \
--name my-app-prod \
--slot staging \
--target-slot production
The new version first arrives in staging (where it warms up), and the swap step promotes it to production without downtime. Combined with the approval gate from part 2, you also have control: the swap happens only after someone has approved.
↩️ Rollback in a few seconds
The big bonus: after swap, the old version does not disappear – it goes to the staging slot. If you notice a problem in production, you swap again and instantly return to the previous version:
az webapp deployment slot swap \
--resource-group rg-myapp-prod \
--name my-app-prod \
--slot staging \
--target-slot production
No rebuild, no redeploy, no panic. Rollback is also a swap.
✅ Some healthy rules
-
Standard or Premium plan – without it you don’t have slots.
-
Keep alwaysOn on the staging slot so that warm-up is fast.
-
Mark environment-specific connection strings as sticky (deployment slot setting).
-
Check the slot’s health before swap – a health check on staging tells you if it’s ready.
-
After swap, don’t immediately delete the version in staging – it’s your rollback net.
Conclusion
Deployment slots transform the release from a planned outage into a non-event: you deploy next to production, warm up, switch instantly, and if something is wrong you return by swapping. Zero downtime, plus rollback in a few seconds.
One piece remains – and it’s the one we kept referring to. In all examples, azure/login authenticated without any password. In part 5, the last in the series, we see exactly how: Managed Identity and OIDC, so that no secret is ever exposed in the pipeline or the application.