Azure’s older GitHub Actions guidance told you to run az ad sp create-for-rbac --sdk-auth, take the
JSON blob it printed, and paste it into a repository secret called AZURE_CREDENTIALS. That blob
contains a client secret with a multi-year lifetime. It is the Azure equivalent of a long-lived
access key, and the same argument applies: replace it with a federated credential.
Logging in without a secret
Section titled “Logging in without a secret”permissions: contents: read id-token: write
steps: - uses: azure/login@v3 with: client-id: ${{ vars.AZURE_CLIENT_ID }} tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}What it doesSigns in to Azure using the workflow's OIDC token, exchanged for an Entra ID access token.
Why we run itNo client secret exists to leak, expire, or rotate. Azure validates the token's issuer and subject against a federated credential you configured on the app registration.
Expected resultA signed-in Azure CLI context for subsequent steps. Nothing sensitive is stored in the repository.
Note that these are vars, not secrets. A client ID, tenant ID and subscription ID are identifiers,
not credentials — they identify which application is trying to sign in, and they are useless without a
federated credential that trusts your specific repository and branch. Storing them as secrets is
harmless but misleading: it implies a leak would matter, and it makes debugging worse because the
values are masked in logs.
App Service, with a staging slot
Section titled “App Service, with a staging slot”Deploying straight to a production App Service replaces the running application in place. Deployment slots give you a safer sequence: deploy to a slot, verify it, then swap.
- name: Deploy to the staging slot uses: azure/webapps-deploy@v2 with: app-name: example-app slot-name: staging package: ./publish
- name: Smoke test the slot run: | for _ in $(seq 1 30); do if curl -fsS "https://example-app-staging.azurewebsites.net/healthz"; then exit 0 fi sleep 10 done echo "::error::staging slot did not become healthy" exit 1
- name: Swap into production run: az webapp deployment slot swap --name example-app --resource-group example-rg --slot stagingThe swap is what makes this valuable. It is a routing change rather than a redeployment, so it takes seconds, and the previously-live application is still sitting in the staging slot afterwards. Rolling back is the same command again — swapping the slots back.
The retry loop matters more than it looks. A freshly deployed slot needs time to start, and a single
curl immediately after deployment tests nothing but the load balancer. Polling until healthy, with a
bounded number of attempts, is the difference between a smoke test and a coin flip.
Azure Functions
Section titled “Azure Functions”- uses: azure/functions-action@v1 with: app-name: example-functions package: ./publishFunctions have their own version of the slot pattern, and the same rule applies: deploy to a slot, verify, swap. The consumption plan adds a wrinkle — a cold start after deployment means the first request can take seconds, so a smoke test needs the same retry loop rather than a single call.
Container Apps
Section titled “Container Apps”For containerised workloads, deploy by digest for exactly the reasons covered in Docker CI:
- name: Deploy the revision run: | az containerapp update \ --name example-app \ --resource-group example-rg \ --image "ghcr.io/OWNER/REPO@${DIGEST}" env: DIGEST: ${{ needs.build.outputs.digest }}Container Apps creates a new revision rather than mutating the old one, which gives a genuine rollback: revisions are retained, and traffic can be shifted back to a previous one without rebuilding anything. That also makes gradual rollout possible — split traffic across two revisions and increase the new one’s share as confidence grows.
Static Web Apps
Section titled “Static Web Apps”Static Web Apps issue a deployment token rather than using the login above. This is one of the few places where a stored secret is still the supported mechanism, so treat it accordingly: scope it to the one resource, store it as an environment secret rather than a repository secret so only the deploy environment can read it, and rotate it on a schedule.
Its pull-request preview feature deploys a preview environment per pull request, which is genuinely useful and carries the usual caveat: a preview built from a fork’s code is running that fork’s code on your infrastructure under your domain. Restrict previews to branches in the repository unless you have thought that through.
Rollback on Azure
Section titled “Rollback on Azure”| Deployment | Rollback | Reliable? |
|---|---|---|
| App Service with slots | Swap back | Yes, seconds |
| App Service without slots | Redeploy the previous package | Only if you kept the package |
| Functions with slots | Swap back | Yes |
| Container Apps | Shift traffic to the previous revision | Yes — revisions are retained |
| Static Web Apps | Redeploy the previous build | Yes, if the artifact still exists |
| Database migration | — | No. |
| Bicep/ARM infrastructure change | Re-deploy the previous template | Sometimes — a delete is not undone by a redeploy |
The pattern is the same as on any cloud: rollback is reliable exactly when the previous state still exists as an immutable object. Slots and revisions are that object; an in-place package deployment is not.
Scoping the role assignment
Section titled “Scoping the role assignment”The federated identity should hold the narrowest Azure RBAC role that completes the deployment, at the narrowest scope:
- Assign at the resource or resource group scope, not the subscription. Contributor on a subscription means the deploy identity can delete anything in it.
- Use a purpose-built role where the built-in ones are too broad. “Website Contributor” is a much smaller grant than “Contributor”.
- Separate app registrations for staging and production, each with its own federated credential restricted to the corresponding environment.
Restricting the federated credential’s subject is the control that stops a branch from deploying to
production — the Azure equivalent of the sub claim condition in
AWS OIDC, and it is covered in
Azure OIDC.
Exercise
Section titled “Exercise”-
Follow Azure OIDC to add a federated credential to an app registration, scoped to your repository’s
productionenvironment. -
Store the client, tenant and subscription IDs as repository variables, not secrets.
-
Write a workflow with
id-token: writethat logs in and runsaz account show. Confirm it succeeds with no secret in the repository. -
Add an App Service with a staging slot. Deploy to the slot, poll
/healthzuntil it responds, then swap. -
Swap back and confirm the previous version returns. That is your rollback, and having run it once before you need it is the entire point.