GitHub Actions Integration in Azure: Automated Build, Test, and Deployment

GitHub Actions Integration in Azure: Automated Build, Test, and Deployment

A beginner-friendly, book-style guide to connecting GitHub Actions with Microsoft Azure. Learn how workflows, runners, and secure passwordless sign-in work, how to deploy to App Service, Functions, containers, and Kubernetes, and how to protect, speed up, monitor, and govern your pipelines in production.

Imagine a bakery that sells bread across a whole city. Every morning, a baker finishes a new recipe, writes it on paper, and walks it to each shop by hand. Some shops get the new recipe at nine, others at noon, and one shop never gets it at all. A few shops make mistakes copying the steps. Customers notice that the bread tastes different depending on where they buy it, and nobody can say exactly which recipe version each shop is using.

Software teams lived like this for years. A developer finished a change, someone built it on a laptop, copied files to a server, clicked through a few screens, and hoped nothing was forgotten. GitHub Actions is a way to replace that hand-carrying with an automatic delivery line. Each time code changes, a robot builds it, tests it, packages it, and delivers it to Microsoft Azure, in the same way, every time, with a written record of what happened.

This guide assumes you know nothing about the topic. Every new term is explained the first time it appears, every big idea has an everyday comparison, and the chapters build on each other. By the end you will understand how GitHub Actions and Azure fit together, how to sign in to Azure without storing passwords, how to deliver to the most common Azure services, how to keep the pipeline safe, quick, and affordable, and how to find and fix problems when a delivery goes wrong.

1The Problem: Delivering Software by Hand

Before meeting any tool, understand the pain it removes. Once the pain is clear, every feature of GitHub Actions looks sensible instead of random.

What goes wrong with manual delivery

Writing code is only half of a developer’s job. The code must also be built (turned into something a computer can run), tested (checked for mistakes), packaged (bundled for delivery), and deployed (placed on the servers where users can reach it). When people do these jobs by hand, five troubles appear again and again.

Trouble 1

Human error

A forgotten step, a wrong server, or a typo in a setting can break the live product. People get tired, and a Friday evening release is the most error-prone moment of the week.

Trouble 2

“Works on my machine”

A build made on one laptop can differ from a build made on another, because tool versions and settings differ. The bug appears only in production.

Trouble 3

Slow feedback

If tests run only before a big release, a mistake made three weeks ago is found late, when it is expensive to fix.

Trouble 4

No record

Nobody can say exactly who deployed what, when, and why. After an incident, the team argues from memory.

Trouble 5

Shared passwords

To deploy by hand or by simple script, someone needs powerful Azure credentials. These get copied into documents, chat messages, and settings pages, where they leak.

Trouble 6

Fear of releasing

When releases are painful, teams release rarely. Rare releases are bigger, and bigger releases are riskier, which makes everyone even more afraid.

Analogy

Think of a modern airport baggage system. Nobody carries suitcases from the check-in desk to the plane by hand. Bags are placed on a belt, scanned, sorted, tagged, and loaded by a machine that follows fixed rules, and each bag leaves a trail that staff can inspect if it goes missing. A delivery pipeline is the baggage system for software: the same belt, the same scans, the same record, for every change.

Continuous integration and continuous delivery

Two phrases describe the cure. Continuous Integration (CI) means that every time a developer shares a change, the project is automatically built and tested, so that mistakes are caught within minutes. Continuous Delivery (CD) means that every change which passes the tests is automatically prepared and can be released with little effort. When the release itself also happens automatically, people often call it continuous deployment. Together these practices are known as CI/CD.

Why Azure is part of the story

Microsoft Azure is a cloud, a very large collection of computers, storage, networks, and ready-made services that you rent by use. Delivering software “to Azure” means placing your application onto one of these services, such as a web hosting service, a function platform, a container platform, or a Kubernetes cluster. Because Azure can be controlled entirely by software commands, it is a natural partner for an automation tool: a pipeline can create the servers, install the application, and check that everything works, without a human opening a web page.

Every change
built and tested automatically
0 passwords
stored in the repository when passwordless sign-in is used
1 record
of who changed what, when, and with what result

An illustrative example

Consider a hypothetical team of eight developers who run an online ticket shop on Azure App Service. Before automation, a release took three hours of careful clicking and happened once a month. After adopting GitHub Actions, every change to the main branch is built and tested in about eight minutes, deployed first to a test copy of the site, and then, after one manager clicks an approval button, to the live site. The team now releases several times a week, and when something does break, the history shows exactly which change caused it.

2Core Concepts: The Vocabulary of GitHub Actions

GitHub Actions has a small vocabulary. Learn these words once, and every later chapter reads easily.

The words, one at a time

TermPlain meaningEveryday comparison
RepositoryThe folder, kept on GitHub, that holds a project’s files and their full historyA filing cabinet that remembers every version of every page
WorkflowAn automated process described in a text file stored inside the repository, in a special folderA recipe card
EventSomething that happens and can start a workflow: a push of code, a pull request, a timer, a manual button, a new releaseA doorbell
JobA group of steps that run together on one machine. A workflow can have several jobsOne station on an assembly line
StepA single task inside a job, either running a command or using a ready-made actionOne instruction on the recipe card
ActionA reusable, shareable building block, such as “sign in to Azure” or “deploy to a web app”A kitchen gadget you borrow instead of building
RunnerThe machine, physical or virtual, that actually executes a jobThe cook who follows the recipe
SecretA protected value, like a key, stored by GitHub and handed to a workflow only when neededA locked key cabinet
VariableA non-secret setting, such as a region name, that workflows can readA label on the shelf
EnvironmentA named target, like “staging” or “production”, which can have its own secrets, rules, and approversA separate kitchen with its own door lock
ArtifactA file produced by one job and saved for later jobs or for download, such as a built packageA finished dish placed on the pass for the next station
MatrixA way to run the same job many times with different settings, such as several versions of a languageBaking the same cake in three oven sizes

Where workflows live

A workflow is written in YAML, a simple text format that uses indentation to show structure. These files live in a folder named .github/workflows at the top of the repository. Because they sit beside the code, they are versioned like code: changes are reviewed, history is kept, and you can see exactly how the delivery process looked on any past day. This idea, often called pipeline as code, is one of the biggest strengths of the approach.

Analogy

Imagine a restaurant chain. The recipe card is the workflow. The ringing doorbell, such as a customer’s order, is the event. Each cooking station is a job, and the instructions at a station are steps. The cook is the runner, and the borrowed gadget, say a professional mixer, is an action. The locked cabinet holds the key to the delivery van, which is the secret. Every kitchen follows the same card, so every plate looks the same.

Events in more detail

The most common events are a push (new commits arrive on a branch), a pull request (someone proposes a change and asks others to review it), a schedule (a timer like every night at two), a manual trigger (a person clicks a button, with optional inputs), a release (a new version is published), and a workflow call (one workflow invokes another). You can narrow an event so that it fires only for certain branches, tags, or changed file paths. For instance, a workflow may run only when files under the application folder change, so that a typo fix in the documentation does not trigger a deployment.

Jobs run in parallel unless told otherwise

By default, jobs in a workflow start at the same time on separate machines. When one job needs the result of another, for example a deployment that must wait for the tests, you declare that dependency, and GitHub waits. This lets you build a shape: several test jobs run side by side, and a single deploy job begins only after all of them pass. Because each job starts on a fresh machine, files do not carry over between jobs automatically. Anything the next job needs must be passed as an artifact or stored somewhere shared, such as a container registry.

i
Tip

Think “fresh machine” every time. A job starts clean, does its work, and the machine is thrown away. This is what makes builds repeatable, and it is also why secrets and files must be handed over deliberately.

3What GitHub Actions Is, and How It Came to Azure

A short history explains why the pieces look the way they do.

From build servers to built-in automation

In the early 2000s, teams ran their own build servers with tools such as Jenkins, which watched for code changes and ran scripts. These servers were powerful but needed constant care: updating, patching, securing, and scaling. Later, hosted CI services such as Travis CI and CircleCI let teams rent this service. GitHub, the most popular place to host code, introduced GitHub Actions in public form in 2018, and it became generally available in November 2019. Its big idea was to place automation directly inside the place where the code and the review conversations already live, with a marketplace of shared actions so that nobody has to write every step from zero.

Microsoft acquired GitHub in 2018, and the two platforms have grown closer since. Microsoft publishes official GitHub Actions for Azure, Azure documentation shows GitHub Actions deployment as a first-class path, and several Azure services can generate a ready-made workflow file for you from their portal pages.

What GitHub Actions gives you

Built in

Next to the code

Pull requests, code review, security alerts, and pipelines live in one place, so no extra server or login is needed.

Reusable

A marketplace of actions

Thousands of shared building blocks cover common tasks, from setting up a language to deploying to a cloud.

Flexible

Many operating systems

Hosted runners are available for Linux, Windows, and macOS, so a project can be tested on all of them.

Scalable

Runners on demand

GitHub provides virtual machines when a job starts, or you can bring your own machines, including ones inside your Azure network.

Safe

Secrets and OIDC

Built-in secret storage, environment protection rules, and passwordless sign-in to Azure reduce the chance of leaked keys.

Visible

Logs and history

Every run keeps a log, a status, and a link to the exact commit and the person who triggered it.

GitHub Actions and Azure Pipelines

Microsoft also offers Azure Pipelines, part of the Azure DevOps service, which provides a similar capability. Both are mature and can deploy to Azure. A team already deeply invested in Azure Boards, Azure Repos, and Azure Pipelines may stay there, while a team whose code is on GitHub usually finds GitHub Actions the natural choice. Chapter 17 compares them in detail. The important point for now is that this is a choice of tooling, not of destination: both can deliver the same application to the same Azure services.

The best pipeline is the one that makes the safe way of releasing also the easy way.

What this guide covers, and what it does not

This guide focuses on the integration: how GitHub Actions authenticates to Azure, what it can deploy, and how to run it safely and well. It does not teach a programming language, and it contains no code listings. Product names, features, prices, and limits change over time, so where this book mentions specifics, treat them as guidance and confirm them in the current GitHub and Microsoft documentation before relying on them.

4Architecture: GitHub, Runners, and Azure

Three worlds meet in this integration: the GitHub platform, the machines that run the jobs, and your Azure subscription. Seeing the boundaries clearly makes security and troubleshooting much easier.

flowchart LR
  DEV["Developer"] -->|"push or pull request"| REPO["GitHub repository"]
  REPO -->|"event"| GHA["GitHub Actions service"]
  GHA -->|"assigns job"| RUN["Runner machine"]
  RUN -->|"requests identity token"| OIDC["GitHub OIDC token issuer"]
  RUN -->|"presents token"| ENTRA["Microsoft Entra ID"]
  ENTRA -->|"short lived access token"| RUN
  RUN -->|"deploys using access token"| ARM["Azure Resource Manager"]
  ARM --> APP["App Service, Functions, Container Apps, AKS"]
  RUN -->|"pushes images"| ACR["Azure Container Registry"]
  RUN -->|"reads secrets"| KV["Azure Key Vault"]
  GHA -->|"logs and status"| REPO
Figure 4.1: How a GitHub Actions run reaches Azure resources

The components

ComponentRole
GitHub repositoryHolds the code and the workflow files, and raises events when things change
GitHub Actions serviceThe control center that reads workflows, decides which jobs to run, queues them, and records results
RunnerThe machine that runs the job’s steps; it contacts GitHub to pick up work, so GitHub does not need to reach into your network
GitHub OIDC token issuerCreates a short-lived signed identity card for a specific job, stating which repository, branch, and environment it belongs to
Microsoft Entra IDAzure’s identity service; it checks the signed card and, if it matches a trust rule you configured, issues an Azure access token
Azure Resource ManagerThe front door for managing Azure resources; every create, update, or deploy request passes through it and is checked against role assignments
Target servicesApp Service, Azure Functions, Azure Container Apps, Azure Kubernetes Service, virtual machines, static websites, and more
Azure Container RegistryA private store for container images produced by the pipeline
Azure Key VaultA protected store for secrets and keys that the application or pipeline needs at run time

Reading the diagram

A developer pushes code to the repository. That raises an event, and the GitHub Actions service picks the matching workflow and assigns each job to a runner. When the job needs to talk to Azure, the runner asks GitHub for a signed identity token. It presents that token to Microsoft Entra ID, which checks it against the trust rules you set up. If they match, Entra ID returns a short-lived Azure access token. With that token the runner calls Azure Resource Manager to deploy the application, pushes container images to the registry, and reads any needed secrets from Key Vault. Finally, results and logs go back to the repository so that everyone can see them.

Analogy

Picture a courier visiting a secure office building. The courier does not own a master key. Instead, the courier’s company issues a signed delivery slip for one parcel and one day. The front desk, which is Entra ID, checks the slip against its list of expected deliveries and hands over a visitor badge valid for an hour. With that badge the courier reaches only the floors that the badge allows. When the hour ends, the badge stops working, and nothing needs to be returned or rotated.

Who owns what

The boundary matters. GitHub owns the workflow engine, the hosted runners, and the identity token issuer. You own the repository content, the workflow files, the Azure subscription, the role assignments, and the trust rules in Entra ID. If you use self-hosted runners, you also own those machines and their patching. This split of responsibility explains many security decisions later in the book: you choose what an Azure identity may do, and GitHub vouches only for who is asking.

5How a Workflow Run Works, Step by Step

Follow a single change from a developer’s keyboard to a running application in Azure, and notice what happens at each hand-off.

sequenceDiagram
  participant D as Developer
  participant G as GitHub
  participant R as Runner
  participant E as Entra ID
  participant A as Azure
  D->>G: Push commit to branch
  G->>G: Match event to workflow file
  G->>R: Queue job and assign runner
  R->>G: Download workflow and fetch code
  R->>R: Build and run tests
  R->>G: Upload build artifact
  R->>G: Request OIDC token for this job
  G-->>R: Signed identity token
  R->>E: Exchange token for access token
  E-->>R: Short lived Azure access token
  R->>A: Deploy package to target service
  A-->>R: Deployment result
  R->>G: Send logs and final status
  G-->>D: Show success or failure on commit
Figure 5.1: The life of one workflow run from push to deployment

The steps in words

1

An event happens

A developer pushes a commit or merges a pull request. GitHub records the event, with details such as the branch name, commit identifier, and the person responsible.

2

GitHub finds matching workflows

It scans the workflow files in the repository at that commit and selects those whose trigger rules match. A repository can have many workflows, such as one for tests and another for deployment.

3

Jobs are queued and a runner is assigned

Each job waits for a free runner with the requested labels, for example “Linux.” Hosted runners are created on demand, while self-hosted runners are chosen from your own pool.

4

The runner prepares itself

It downloads the job instructions, fetches the repository code, and sets up any requested tools. The workflow’s first steps usually do a checkout of the code and install the language toolchain.

5

Build and test

The steps compile the code, run tests, and check quality rules. If any step fails, by default the job stops and the run is marked as failed, so a broken change never reaches deployment.

6

Package and hand over

The build output is uploaded as an artifact, or an image is pushed to a registry, so that the deployment job can use exactly the same bytes that were tested.

7

Sign in to Azure

The deployment job obtains an identity token from GitHub and exchanges it with Entra ID for a short-lived access token. No stored password is involved.

8

Deploy and verify

The job tells Azure to place the package on the target service, then performs a health check, such as calling a test page, to confirm the new version responds correctly.

9

Report and clean up

The runner sends logs and status back to GitHub, which shows a green tick or red cross beside the commit. A hosted runner is destroyed, leaving no trace of the secrets it used.

Build once, deploy many

One golden rule shapes good pipelines: build the package once and deploy the same package to every environment. If you rebuild for production, you are shipping something different from what you tested, even if the code is identical, because the tools, dependencies, or timing may differ. By storing the output of the first build as an artifact or a container image with a unique tag and promoting that one item through test, staging, and production, you can trust that what you checked is what you released.

!
Watch out

Beware of workflows that rebuild the application inside the production job. A rebuild can quietly pull newer dependency versions, so production runs software that was never tested. Always deploy the stored, tested artifact.

6Runners: Where the Work Actually Happens

A workflow is only instructions. The runner is the machine that carries them out, and the choice of runner affects speed, cost, security, and network reach.

GitHub-hosted runners

A GitHub-hosted runner is a fresh virtual machine that GitHub creates for each job and destroys afterward. Standard images come with many tools pre-installed, such as common languages, build tools, the Azure command line tool, Docker, and Git. You select the machine type by a label in the workflow, for example a recent Ubuntu Linux version, Windows Server, or macOS. Because each job gets a clean machine, there is no leftover state, and GitHub handles patching and scaling. The trade-off is that hosted runners live on the internet, not inside your private network, and a limited free allowance of run time applies, with usage beyond it billed according to your plan.

GitHub also offers larger runners with more processors and memory, for heavy builds, and some options that let hosted runners join an Azure virtual network so that they can reach private resources. Availability, plan requirements, and prices vary, so check the current documentation.

Self-hosted runners

A self-hosted runner is a machine you provide and register with GitHub. It could be a virtual machine in Azure, an on-premises server, or a container on a Kubernetes cluster. Self-hosted runners are useful when a job must reach private systems that the internet cannot see, such as a database inside a locked-down virtual network, when you need special hardware or software, or when you want to control cost on very heavy usage. The price is responsibility: you must patch them, secure them, scale them, and clean them between jobs.

Analogy

A hosted runner is like hiring a taxi: a clean car arrives, takes you where you asked, and disappears, and the company handles maintenance. A self-hosted runner is like owning a car: it can drive into your private garage and carry special equipment, but you pay for repairs, fuel, and cleaning, and you must make sure no stranger leaves something unpleasant on the back seat.

Running runners on Azure

Option

Virtual machines

Simple to understand: install the runner program on an Azure VM inside your network. Works for low or steady volume, but each VM must be patched and cleaned by you.

Option

Virtual machine scale sets

A pool of identical machines that grows and shrinks with demand. Often combined with automation that creates a new, clean machine per job.

Option

Kubernetes with Actions Runner Controller

An open-source controller that starts runner pods on demand in an AKS cluster and removes them when finished, giving clean, short-lived runners at scale.

Option

Container Apps jobs

Event-driven runner containers that start when jobs queue and stop when idle, with no cluster to manage. A growing pattern for teams that want elasticity without Kubernetes.

Ephemeral beats persistent

The safest self-hosted setup uses ephemeral runners: each runner takes exactly one job and is then destroyed. A persistent runner that handles many jobs may leave files, credentials, or poisoned caches behind that the next job, possibly from a different repository or a different contributor, can read. If you must use persistent runners, restrict them to trusted repositories and clean the workspace thoroughly after every job.

Choosing between them

QuestionGitHub-hostedSelf-hosted
Setup effortNoneYou install, register, and maintain
Cleanliness between jobsFresh machine every timeDepends on your design; ephemeral recommended
Access to private networksLimited unless special networking options are usedDirect, inside your own network
Special hardware or softwareLimited choicesAnything you can install
Cost modelPay per minute beyond the included allowancePay for the Azure compute, plus your team’s time
Security responsibilityMostly GitHubMostly you
!
Watch out

Never attach self-hosted runners to public repositories without strict controls. Anyone can open a pull request, and a workflow triggered by an untrusted pull request could run attacker-chosen commands on a machine inside your network.

7Signing In to Azure: Secrets, Service Principals, and OIDC

The most important question in this whole integration is simple: how does a robot prove to Azure that it is allowed to deploy? The answer you choose decides how safe your pipeline is.

First, what is an identity in Azure?

Every request to Azure must come from someone or something that Azure can recognise. For people, this is a user account in Microsoft Entra ID (the new name for Azure Active Directory). For software, Azure uses a service principal, which is an identity for an application, and a managed identity, which is an identity that Azure creates and looks after for a resource. After Azure knows who is asking, it checks role assignments: lists that say which identity may do which actions on which resources. A role such as “Contributor” on one resource group means “may create and change things in this group, but nowhere else.”

Analogy

A service principal is like an employee badge issued to a robot, and a role assignment is the list of doors that badge can open. Entra ID is the security office that issues badges. The hard question is how the robot shows its badge safely without the badge being stolen or copied.

The old way: a stored secret

For years, the usual method was to create a service principal, generate a long-lived client secret (a password for the application), and paste it into GitHub as a repository secret. The workflow read that secret at run time and used it to sign in. This works, but it has serious weaknesses. The secret is valid for months or years, so if it leaks through a log, a misconfigured workflow, a compromised developer machine, or a malicious action, an attacker can use it from anywhere until someone notices. Secrets expire, which causes surprise outages on the day they run out. And someone must remember to rotate them, repeatedly, in every repository that uses them.

Another older method was the publish profile, a downloadable file with credentials for a single web app. It is convenient but is also a long-lived secret with broad power over that app, and Microsoft documentation now increasingly recommends identity-based approaches instead. Check the current guidance for your service.

The better way: OpenID Connect (OIDC)

OpenID Connect is a standard way for one system to vouch for the identity of something to another system. With OIDC, GitHub acts as the one who vouches, and Entra ID acts as the one who trusts. Instead of storing a password, you configure a federated credential on the Azure identity. This is a trust rule that says, in effect: “Accept identity tokens issued by GitHub, but only when they say they come from this organisation, this repository, and this branch or environment.”

At run time the workflow requests a short-lived signed token from GitHub. The token contains claims, which are facts such as the repository name, the branch, the environment, and the workflow that is running. The runner sends this token to Entra ID, which checks the signature, checks that the claims match the federated credential, and then issues an Azure access token that lasts only for a short time, usually around an hour. There is nothing to leak, nothing to rotate, and nothing to expire on a quiet Sunday.

flowchart TD
  S["Workflow job starts with permission to request an identity token"] --> T["GitHub issues signed token with claims"]
  T --> C["Runner sends token to Microsoft Entra ID"]
  C --> V{"Do issuer and claims match a federated credential"}
  V -->|"Yes"| OK["Entra ID issues short lived Azure access token"]
  V -->|"No"| NO["Sign in refused and job fails"]
  OK --> USE["Deploy within the roles assigned to the identity"]
  USE --> END["Token expires and nothing remains to steal"]
Figure 7.1: The passwordless sign-in flow using OIDC federation
Analogy

The stored secret is a house key hidden under the doormat: anyone who finds it can enter at any time, forever, until you change the lock. OIDC is a visitor system with a front-desk guard who has a list. A courier arrives with a signed slip naming the sender, the day, and the parcel. The guard checks the slip against the list and gives a one-hour badge. A stolen slip is useless to anyone else because it names a specific sender and a specific job.

Setting up OIDC, in plain steps

1

Create an identity in Azure

Register an application in Entra ID, which creates a service principal, or create a user-assigned managed identity. Either can hold federated credentials.

2

Add a federated credential

Tell Entra ID to trust GitHub’s token issuer for a named organisation and repository, and to match on a branch, a tag, a pull request, or best of all a named environment such as production.

3

Assign minimal roles

Give the identity only the roles it needs, on the smallest scope that works, such as one resource group or even one web app.

4

Store three non-secret identifiers in GitHub

The client identifier of the identity, the tenant identifier of your Entra directory, and the subscription identifier. These are identifiers, not passwords, though many teams still keep them as variables or secrets for tidiness.

5

Allow the workflow to request a token

In the workflow, grant the job the permission that lets it ask GitHub for an identity token, usually written as granting write access for the identity token. Without this permission, no token is issued.

6

Use the official login action

The Azure login action performs the exchange and leaves the runner signed in for later steps and actions.

Choosing the subject: what the trust rule matches

The most subtle part of OIDC is deciding what the federated credential matches. The token’s subject claim has a pattern that depends on how the job was triggered, and a federated credential accepts exactly one subject value. Common choices are shown below.

Match onMeaningWhen to use
EnvironmentOnly jobs that target a named GitHub environment, such as productionBest for deployments, because the environment’s approvals and protection rules also apply
BranchOnly jobs running on a given branch, such as mainSimple setups; weaker, because branch rules may be changed by anyone with repository admin rights
TagOnly jobs triggered by a tag such as a release tagRelease-by-tag processes
Pull requestJobs triggered by pull requestsRead-only checks such as previewing infrastructure changes; give very limited roles

Because you need one federated credential per subject, a repository that deploys to three environments usually has three credentials, ideally mapping to three separate identities with different permissions: a low-power one for test, and a more carefully guarded one for production.

Managed identities and workload identity

A user-assigned managed identity works nicely as the deployment identity, because Azure manages its lifecycle and it has no secret at all, but it can still hold federated credentials for GitHub. For the application itself, once deployed, use a managed identity so the app can reach databases and vaults without any stored password. For pipelines that run on self-hosted runners inside Azure, the runner’s own managed identity can sign in directly, though this identity is then shared by every job on that machine, which is a point of caution.

MethodStored secret?Lifetime of credentialRecommendation
Client secret in GitHub secretsYesMonths or yearsAvoid for new work
Certificate in GitHub secretsYesMonths or yearsBetter than a password, still avoid if OIDC is possible
Publish profileYesUntil resetLegacy; use only if nothing else works
OIDC federated credentialNoMinutes to an hour per runRecommended default
Managed identity on a self-hosted Azure runnerNoShort-lived tokensGood for in-network runners, but share with care
i
Tip

If you see an error saying that no matching federated identity record was found, the usual cause is a mismatch between the subject the workflow produced and the subject you registered. Check whether the job ran on a branch or in an environment, and spell the names exactly, because they are case-sensitive.

8The Azure Actions Toolbox

Microsoft and the community publish ready-made actions for Azure. Knowing the main ones saves you from reinventing common steps.

What an action is, and where it comes from

An action is a packaged piece of automation that a workflow step can call by name and version, for example “the Azure login action, version 2”. Actions live in public GitHub repositories (or private ones within your organisation). When a workflow runs, the runner downloads the named action and executes it. This sharing is powerful, but it also means that your pipeline runs other people’s code, a point we revisit in the security chapter.

Common Azure-related actions

Sign in

Azure login

Signs the runner in to Azure using OIDC or other credentials, so later steps can run Azure commands or other actions.

Run commands

Azure CLI action

Runs Azure command line commands in a prepared, signed-in environment, handy for small management tasks and scripts.

Run commands

Azure PowerShell action

The same idea for teams that prefer PowerShell.

Web apps

Web apps deploy

Deploys a package, folder, or container image to an Azure App Service web app, including to a named deployment slot.

Serverless

Functions action

Deploys code to an Azure Functions app.

Containers

Container Apps deploy

Builds and deploys an application to Azure Container Apps from source or from an existing image.

Kubernetes

AKS and Kubernetes actions

Set the cluster context, prepare manifests, and deploy with strategies such as canary or blue-green.

Static sites

Static Web Apps deploy

Builds and publishes front-end sites, often with preview environments created for each pull request.

Infrastructure

Deployment actions for templates

Deploy Bicep or ARM templates to a resource group or subscription, and preview their effect first.

Registry

Container registry login

Signs the runner in to Azure Container Registry so that images can be pushed and pulled.

Secrets

Key Vault action

Reads secrets from Key Vault into masked variables for the duration of a job.

Quality

Security and policy scanners

Community and Microsoft actions check templates, containers, and code for risky settings.

!
Watch out

Names, versions, and the maintenance status of individual actions change over time. Some older Azure actions were retired or replaced by newer ones, and a few capabilities moved into the Azure CLI. Before building on an action, check its repository for recent activity and its documentation for the current recommended approach.

Actions versus plain commands

You never have to use an action. Every deploy step can also be done by running Azure command line commands directly from a step. Actions are convenient because they hide detail, validate inputs, and give consistent logging. Commands are flexible and transparent, and they work the same on a developer laptop, which helps with troubleshooting. Many teams use a mix: actions for login and deployment, commands for custom checks.

Reusable workflows and composite actions

As the number of repositories grows, copying the same workflow text into each one becomes a maintenance nightmare. GitHub offers two ways to share. A reusable workflow is a whole workflow that other workflows can call, passing inputs and secrets, ideal for standard “build and deploy to Azure” procedures. A composite action bundles several steps into one action that can be used inside any job. A platform team can publish a small set of approved building blocks, so that product teams assemble pipelines from vetted parts and security improvements reach everyone with a single change.

Analogy

Reusable workflows are like franchise operating manuals. The head office writes the approved way to run a shop, and every branch follows it with local details filled in. When the head office improves the fire-safety procedure, every branch gets it at once, instead of fifty managers editing fifty different notebooks.

Pin versions

Every use of an action names a version, such as a major version tag, a more precise tag, or a full commit identifier. A major version tag moves, so you silently receive updates, which is convenient but means that a compromised or buggy release reaches you immediately. A full commit identifier never moves, so you decide when to update. Security-minded teams pin to full commit identifiers, and let an automatic tool such as Dependabot propose upgrades for review.

9Deploying to Azure Services

Azure offers many places to run an application. Each has a different delivery path from GitHub Actions. This chapter tours the most common ones.

Azure App Service

App Service is a managed web hosting platform. You hand it a package of your web application, and Azure runs it, patches the operating system, scales it, and gives it an address. A pipeline builds the application, uploads the package as an artifact, and a deployment job signs in with OIDC and uses the web apps deployment action to publish it. App Service supports deployment slots: extra copies of the app, such as “staging,” that run alongside the live one. The pipeline deploys to the staging slot, tests it there, and then swaps the slots, which switches users to the new version almost instantly and keeps the old version one swap away for a quick rollback. Many teams enable this approach by default for production web apps.

Analogy

A slot swap is like a theatre with two identical stages behind a rotating platform. The crew builds and rehearses the next scene on the hidden stage. When it is perfect, the platform rotates, and the audience sees the new scene in one smooth move. If something looks wrong, rotate back.

Azure Functions

Azure Functions runs small pieces of code in response to events, such as a message arriving or a timer firing, without you managing servers. Deployment from GitHub Actions is similar to App Service: build and package, then publish using the functions action. Functions can also have slots on certain hosting plans. Because functions often connect to storage and queues using identities, make sure the deployment identity only changes the function app, while the function app’s own managed identity holds the data permissions.

Azure Container Apps

Azure Container Apps runs containers without exposing the details of Kubernetes. The pipeline builds a container image, pushes it to a registry, and then updates the container app to use the new image. Container Apps keep revisions, which are immutable snapshots of each version, and can split traffic between revisions by percentage, which supports canary releases. A new revision can receive 10 percent of traffic first, and if the health looks good, the share grows to 100 percent.

Azure Kubernetes Service (AKS)

AKS is a managed Kubernetes cluster. Kubernetes is a system that runs many containers across a group of machines, restarts them if they crash, and scales them. For AKS, the pipeline builds and pushes an image, signs in to Azure, obtains access to the cluster, and applies Kubernetes descriptions of the desired state, using manifests, Helm charts, or Kustomize overlays. Kubernetes then rolls out the change gradually, replacing old containers with new ones and stopping if health checks fail. A newer approach called GitOps flips the direction: the pipeline only updates a Git repository holding the desired state, and a tool inside the cluster, such as Flux (available as an AKS extension) or Argo CD, pulls the change in. Pull-based delivery means the pipeline never needs cluster-admin access from outside.

Azure Static Web Apps

For front-end sites built with frameworks like React, Angular, or Vue, Static Web Apps hosts the files on a global network and can attach serverless back-end functions. When you create one, Azure can add a workflow to your repository automatically. A helpful feature is the preview environment: each pull request gets its own temporary copy of the site with a unique address, so reviewers can click through the change before it merges, and the copy is deleted when the pull request closes.

Virtual machines and other targets

Some systems still run on plain virtual machines. Pipelines can deploy there with command-line tools, remote command execution, or by building machine images and rolling out scale sets. Other common targets include Azure Spring Apps and Azure SQL databases (for schema changes), API Management (for API definitions), and Azure Data Factory or Synapse (for data pipelines). The same pattern repeats: build, store an artifact, sign in with a scoped identity, deploy, and verify.

TargetWhat the pipeline deliversNice rollout feature
App ServiceWeb package or container imageDeployment slots and swap
Azure FunctionsFunction package or containerSlots on supported plans
Container AppsContainer imageRevisions with traffic splitting
AKSImage plus Kubernetes manifestsRolling updates, canary strategies, GitOps
Static Web AppsBuilt front-end filesPreview environment per pull request
Virtual machinesPackages, scripts, or machine imagesScale set rolling upgrades
DatabasesSchema migrationsPreview and approval before applying

Let the portal write the first draft

Several Azure services, such as App Service and Static Web Apps, have a Deployment Center or similar page where you pick a GitHub repository and branch, and Azure creates the workflow file and the identity configuration for you. This is a very good way to learn, as long as you then review what it created, tighten the permissions, and move any old-style secrets to OIDC.

10Container Pipelines with Azure Container Registry

Containers are the most common way to package modern applications. Here is how a container pipeline flows from source code to a running service.

What a container image is

A container image is a sealed package holding an application together with everything it needs to run: the runtime, libraries, and settings. Wherever the image runs, it behaves the same. A registry is a warehouse for images, and Azure Container Registry (ACR) is Azure’s private registry. Each image has a name, plus a tag such as a version label and an immutable digest, a fingerprint computed from its contents.

Analogy

A container image is like a sealed, labelled lunchbox. Inside are the food, the cutlery, and the napkin. Anyone who opens it anywhere gets exactly the same meal. The registry is the refrigerated warehouse where lunchboxes are stored until the delivery vans collect them, and the digest is a tamper-proof seal number.

flowchart LR
  SRC["Source code in GitHub"] --> BLD["Build image on runner"]
  BLD --> TST["Run tests and scan image"]
  TST --> PUSH["Push image to Azure Container Registry"]
  PUSH --> SIGN["Sign or attest image"]
  SIGN --> DEVD["Deploy to development"]
  DEVD --> STGD["Deploy same image to staging"]
  STGD --> APPR{"Approval required"}
  APPR -->|"Approved"| PRODD["Deploy same image to production"]
  APPR -->|"Rejected"| STOP["Stop and notify team"]
  PRODD --> HEALTH["Health checks and monitoring"]
Figure 10.1: A container delivery pipeline from source to production

The stages

1

Build the image

The runner builds the image from instructions stored with the code. A build cache keeps repeated builds fast, and multi-stage builds keep the final image small by leaving build tools behind.

2

Test and scan

Unit tests run on the code, and a scanner checks the image for known weaknesses in its packages. Findings above an agreed severity can block the pipeline.

3

Push to ACR

The pipeline signs in to Azure with OIDC, and the identity needs only permission to push images to that registry, not to manage the whole subscription.

4

Tag with something meaningful

Use the commit identifier or a build number so that every image is traceable to one source version. Avoid relying on a moving tag like “latest” in production.

5

Promote the same image

Development, staging, and production reference the same digest. Only configuration changes between environments, and configuration comes from environment settings and vaults, not from rebuilding.

6

Deploy and observe

The target service pulls the image from ACR using its own managed identity, and the pipeline verifies health before finishing.

Registry safety features

  • No admin user. ACR has an optional shared admin account with a password. Leave it off and use identities and role assignments such as a push role for the pipeline and a pull role for the runtime.
  • Private access. Premium registries support private endpoints, so images travel only over your private network.
  • Vulnerability scanning. Microsoft Defender for Containers can scan images in the registry and running in clusters.
  • Content trust and signing. Image signing, including newer open standards, lets clusters accept only images produced by your trusted pipeline.
  • Retention rules. Policies can delete old untagged images, preventing the registry from growing forever and costing more.
  • Geo-replication. Premium registries can replicate images to several regions, so clusters pull from nearby copies and survive a regional outage.

Provenance and software bills of materials

Modern pipelines also record provenance: signed statements that say which workflow, from which commit, on which runner, built this exact image. They may also generate a software bill of materials (SBOM), a list of every component inside. GitHub offers artifact attestations for this purpose. Later, a deployment gate can verify that an image really came from your official pipeline before it is allowed to run, which defends against tampering between build and deploy.

i
Tip

Promote by digest, not by tag. A tag can be moved to point at a different image, but a digest cannot change without changing the image itself. What you tested is then, provably, what you shipped.

11Infrastructure as Code in the Pipeline

Before an application can be deployed, its servers, networks, and databases must exist. Infrastructure as code lets the pipeline build those too, with the same discipline as application code.

The idea

Infrastructure as code (IaC) means describing cloud resources in text files rather than clicking through a portal. The description says “I want a web app, a database, and a vault, with these settings,” and a tool makes Azure match it. Because the description is a file, it can be reviewed, tested, versioned, and run again to produce an identical copy, such as a test environment that mirrors production.

Analogy

Clicking through a portal is like building a house by walking around and placing bricks from memory. Infrastructure as code is like working from architectural blueprints: the plan is reviewed before any brick is laid, and the same plan can build a second house in another city that matches the first.

The common tools

Azure native

Bicep

A readable language created by Microsoft that compiles to ARM templates. It is only for Azure, offers excellent day-one support for new services, and needs no state file.

Azure native

ARM templates

The original JSON-based format that Azure uses under the hood. Verbose, but still found widely.

Multi-cloud

Terraform and OpenTofu

Popular tools that work across many clouds with a plan-and-apply workflow and a state file that records what exists. Terraform’s license changed in 2023, which led to the community-governed OpenTofu fork.

Programming languages

Pulumi

Describes infrastructure with general-purpose programming languages instead of a special format.

Preview before you change

The safest pattern uses two stages. On a pull request, the pipeline runs a preview that shows what would change, with Bicep’s what-if feature or Terraform’s plan, and posts it where reviewers can read it. Reviewers can then see “this change will delete a database” before it happens. After the pull request is approved and merged, the deployment stage applies exactly the reviewed change, in the protected environment.

1

Pull request opened

The workflow validates and lints the templates, scans them for risky settings such as public storage, and runs a preview using a read-only identity.

2

Humans review the preview

Reviewers read the code and the predicted changes together. Surprising deletions or replacements are caught here.

3

Merge triggers apply

The deployment job targets a protected environment, waits for approval if required, and applies the change with a more powerful, but still scoped, identity.

4

Verify and record

After applying, a check confirms that the real state matches the intent, and the run log becomes the audit record.

State, drift, and ownership

Terraform and similar tools keep a state file, a record of what they believe exists. This file can contain sensitive values and must be stored remotely and securely, for example in an Azure Storage account with locking, encryption, access limited to the pipeline identity, and versioning. Drift happens when someone changes resources by hand in the portal, so reality no longer matches the code. A scheduled workflow that runs a preview nightly and alerts on differences catches drift early. Equally important is deciding who owns each layer: a platform team may own networks and shared services, while application teams own their app resources, each with separate pipelines and separate permissions.

Separate infrastructure from application pipelines

Changing infrastructure is riskier and rarer than changing application code. Keep their pipelines separate so that a quick application fix does not accidentally touch networks and databases, and so that the identity used for applications does not need broad rights. The application pipeline should hold only the permissions necessary to deploy to resources that already exist.

!
Watch out

Giving a pipeline Owner rights on a whole subscription just to make a first deployment succeed is a common shortcut and a dangerous one. If that pipeline is ever compromised, the attacker controls everything. Start with the smallest role and scope that works, and add rights only when a specific failure shows what is missing.

12Environments, Approvals, and Promotion

Real systems have more than one copy. A safe pipeline moves a change through them in order, with the right people and rules guarding each door.

Why several environments

An environment is a separate copy of your system for a specific purpose. Development is where engineers try things freely. Test or QA is where automated and manual checks run. Staging mirrors production as closely as possible, so surprises appear there instead of in front of customers. Production is the live system. Each step up the ladder is stricter and more carefully guarded. In Azure, environments are typically kept in separate resource groups or, for stronger isolation, separate subscriptions, each with their own identities and network rules.

GitHub environments

GitHub has a feature also called environments. Each GitHub environment is a named target, such as “production”, that you attach to a deployment job. It brings several protections.

Rule

Required reviewers

The job pauses until named people or teams approve. This gives a human gate before production.

Rule

Wait timer

Adds a delay before deployment starts, giving time to cancel.

Rule

Branch restrictions

Only certain branches or tags may deploy to this environment, such as the main branch.

Scope

Environment secrets and variables

Values available only to jobs that target the environment, so production settings are not visible to a test job.

Link

OIDC subject

The environment name appears in the identity token, so Azure can trust only production-environment jobs for the production identity.

Custom

Custom protection rules

Integrations can add checks, such as confirming that a monitoring system shows no open incidents. Availability of some rules depends on your GitHub plan and repository type, so verify before designing around them.

flowchart LR
  PR["Pull request"] --> CI["Build and test"]
  CI --> MERGE["Merge to main"]
  MERGE --> ART["Build once and store artifact"]
  ART --> DEV["Deploy to development environment"]
  DEV --> SMOKE["Smoke tests"]
  SMOKE --> STG["Deploy to staging environment"]
  STG --> E2E["End to end and performance tests"]
  E2E --> GATE{"Reviewer approval"}
  GATE -->|"Approved"| PROD["Deploy to production environment"]
  GATE -->|"Rejected"| HALT["Halt and investigate"]
  PROD --> VERIFY["Post deployment verification"]
  VERIFY -->|"Healthy"| DONE["Release complete"]
  VERIFY -->|"Unhealthy"| ROLL["Automatic rollback"]
Figure 12.1: Promoting one build through environments with an approval gate and rollback

Branching and release strategies

How you organise branches shapes how pipelines trigger. Two popular approaches are worth knowing.

Trunk-based development

  • Developers merge small changes to the main branch often
  • Main is always releasable and deploys automatically through environments
  • Feature flags hide unfinished work
  • Simple pipelines and fast feedback
  • Requires strong automated tests and discipline

Long-lived release branches

  • Separate branches for each release or environment
  • Feels safer to teams with slow, manual testing
  • Changes pile up and merge conflicts grow
  • Risk of “what exactly is in production?” confusion
  • Releases get bigger and less frequent

Preview environments for pull requests

A powerful pattern creates a short-lived copy of the application for each pull request. Reviewers click a link and see the change running, and a clean-up workflow deletes the copy when the pull request closes. Static Web Apps supports this natively, and Container Apps, App Service slots, or temporary resource groups can do it for other kinds of applications. Because these copies cost money while they exist, always pair creation with automatic deletion, and give them tags and a time limit.

Approvals that mean something

An approval is only useful if the approver has the information to decide. Good pipelines show reviewers what is changing, such as the commit list, test results, the infrastructure preview, and the staging verification. They also ensure that the person who wrote the change is not the only one who can approve it, a rule often called separation of duties, and many organisations need this for audit reasons. GitHub can prevent the person who triggered a deployment from approving their own run.

Concurrency control

If two runs deploy to the same environment at the same time, they can fight, and the older code might land after the newer code. GitHub’s concurrency setting names a group, such as the environment name, and ensures only one run in the group is active at a time. For deployments, it is usually best to queue rather than cancel the one in progress, because cancelling halfway through a deployment can leave a system half-updated. For pull request builds, cancelling older runs when a new commit arrives is wise and saves minutes.

i
Tip

Make each environment look as similar as possible to production, differing only in size and in data. If staging uses a different database engine or network layout, its green result tells you less than you think.

13Security: Protecting the Pipeline

A deployment pipeline holds the keys to production. Attackers know it, so a pipeline must be designed as a high-value target, not as a convenience script.

Why pipelines attract attackers

A pipeline runs code, often from many contributors and many third-party sources, on a machine that can reach secrets and cloud accounts. If an attacker can alter what the pipeline runs, they can steal credentials, insert malicious code into the product, or damage the cloud. Real incidents across the industry have included compromised popular actions that leaked secrets from the logs of thousands of workflows, and build systems used as stepping stones to customers. Security here is about shrinking what any single piece can do when it is compromised.

Analogy

Think of a bank vault with a delivery door. The bank can have the strongest vault in the world, but if the delivery door lets any visitor roam freely and the delivery staff hold copies of the master key, then the vault is irrelevant. The pipeline is the delivery door, and it needs its own guards, its own limited keys, and a list of who may enter.

Layer 1: least privilege on the GitHub side

  • Restrict the workflow token. Every run receives an automatic token for talking to GitHub itself. Set the default to read-only and let each workflow or job request only the extra permissions it needs.
  • Protect branches. Require pull requests, reviews, and passing checks before merge on the main branch, so that nobody can push a change straight into production.
  • Protect workflow files. Use code ownership rules so that changes to the workflow folder need review by a platform or security team. A changed workflow is a changed deployment.
  • Limit who can approve. Keep the list of production reviewers small and meaningful.

Layer 2: least privilege on the Azure side

  • One identity per environment and purpose. A test pipeline should never hold production rights.
  • Narrow scopes. Assign roles at the resource or resource group level, not the whole subscription.
  • Custom roles where built-in roles are too broad, granting only the specific actions needed.
  • Bind trust to environments. Federated credentials for production should match only the production environment, which is protected by approvals and branch rules.
  • Separate “plan” from “apply”. Previews can use a read-only identity, while only the protected deploy job gets write rights.

Layer 3: trust in the code that runs

RiskWhat can happenDefence
Third-party action compromisedA new release steals secrets or alters buildsPin to full commit identifiers, review updates, prefer official or verified publishers, allow-list approved actions at the organisation level
Malicious pull request from a forkAttacker code runs with access to secretsUnderstand that workflows from forks run with limited rights by default; never feed untrusted pull request code into a privileged context
Script injectionA pull request title or branch name containing commands is pasted into a script and executedPass untrusted text through environment variables, not directly into command text; validate inputs
Dependency tamperingA compromised library enters the buildLock versions, use Dependabot and dependency review, scan with software composition analysis
Secrets in logsA secret printed by mistake is visible to readersRely on automatic masking, never print secrets, avoid transforming secrets into forms that evade masking
Poisoned cacheA malicious run saves a bad cache that a trusted run later restoresScope caches by branch and trust level; do not restore untrusted caches in deploy jobs
Persistent self-hosted runnerLeftover files or processes leak across jobsUse ephemeral runners and isolate by repository trust

Secrets handling rules

Prefer having no secrets at all: OIDC for Azure and managed identities for applications remove most of them. Where a secret is unavoidable, store it in Azure Key Vault or GitHub’s encrypted secrets, scope it to the environment that needs it, and give it an owner and a rotation date. GitHub automatically masks the exact values of stored secrets in logs, but masking cannot protect transformed values such as an encoded copy, so never print secrets for debugging. Turn on secret scanning and push protection in GitHub so that accidental commits of keys are caught, ideally before they are even pushed.

Code scanning and supply chain tools

GitHub

Code scanning (CodeQL)

Analyses source code for security flaws and shows alerts on pull requests.

GitHub

Dependabot

Alerts about vulnerable libraries and opens pull requests to update them, including for the actions your workflows use.

GitHub

Dependency review

Shows the risk of new dependencies in a pull request before they merge.

GitHub

Secret scanning

Detects committed credentials and can block pushes that contain them.

Azure

Defender for Cloud

Checks resource configurations, containers, and DevOps connections for risk. Features and plans vary, so review current options.

Azure

Azure Policy

Blocks or flags non-compliant resources no matter who or what creates them, a safety net behind the pipeline.

Network considerations

If the target resources have no public access, such as databases behind private endpoints, a hosted runner on the internet cannot reach them. You then either use runners inside your Azure network, use hosted runners that GitHub can attach to your virtual network, or restructure so that the pipeline never touches the private resource directly, for example by pushing an image to a registry and letting the private cluster pull it. Avoid the shortcut of opening firewalls to all of GitHub’s published address ranges, since those ranges are shared by every GitHub user.

!
Watch out

Be very careful with workflow triggers that run with secrets on code from outside contributors. A trigger designed to give pull request workflows extra rights, if combined with checking out the pull request’s own code and running it, can hand secrets to an attacker. When in doubt, split the workflow: an unprivileged one that runs the untrusted code, and a privileged one that only handles trusted results.

14Performance and Cost: Fast, Frugal Pipelines

Slow pipelines waste developer time and money. Fast pipelines encourage frequent, small, safe releases.

What you pay for

GitHub-hosted runner time is metered in minutes, with a multiplier that depends on the operating system, since Linux minutes are the cheapest and Windows and macOS usually cost more. Each plan includes an allowance, and public repositories on standard runners have historically been free, but allowances and rates change, so check the current pricing. Storage for artifacts and caches is also limited and billed beyond the included amount. On the Azure side, you pay for any self-hosted runner machines, for the registry, for preview environments while they exist, and for the test environments that the pipeline deploys.

Speeding things up

1

Cache dependencies

Downloading the same libraries every run wastes time. A cache saves them between runs, keyed by a fingerprint of the dependency list, so it refreshes only when dependencies change.

2

Run independent jobs in parallel

Unit tests, linting, and security scans rarely depend on each other. Running them side by side shortens the total time to that of the slowest.

3

Split large test suites with a matrix

Divide tests into groups that run on separate machines at once, while balancing the groups so none is much slower than the rest.

4

Trigger only when relevant

Use path filters so that a change in documentation does not rebuild the whole system. In large repositories with many applications, each application gets its own filtered workflow.

5

Cancel superseded runs

When a developer pushes three commits in a row, only the last needs to finish for a pull request check.

6

Use layer caching for images

Order container build instructions so rarely changing layers come first and the build cache can reuse them.

7

Right-size runners

A larger runner may finish a heavy build in a third of the time at about the same total cost, since you pay for minutes used. Measure before choosing.

8

Fail fast

Put the quickest, most likely failures first, such as syntax checks, so that obvious problems stop the run in seconds.

Analogy

Pipeline speed is like a hospital emergency room. Quick triage at the front door sends obvious cases to the right place at once. Running several departments in parallel avoids a single long queue. Keeping common supplies in a nearby cupboard, which is a cache, avoids long walks to the warehouse. And a clean, well-labelled floor plan means nobody wastes time searching.

Saving money

  • Prefer Linux runners where possible, since they are cheaper per minute than Windows or macOS.
  • Set job time limits. A hung job can burn minutes for hours. Setting a timeout of, say, 20 minutes on a job that normally takes five prevents runaway charges.
  • Shorten artifact and log retention to what you need for audits.
  • Clean up preview environments automatically on pull request close and with a nightly sweep for orphans.
  • Scale self-hosted pools to zero when idle, using autoscaling.
  • Use scheduled workflows sparingly. A job that runs every five minutes uses over 8,000 runs a month.
  • Review usage reports in your GitHub organisation to find which workflows consume the most minutes.

A worked example

These numbers are invented to show the arithmetic only. Suppose a team of 20 developers triggers 40 pull request runs per day, and each run takes 12 minutes on a Linux runner. That is 480 minutes a day, or about 10,000 minutes in a 21-working-day month. If caching and parallel jobs cut each run to six minutes, the monthly total halves to roughly 5,000 minutes. More importantly, developers wait six minutes less per check, which is 40 times six, or 240 minutes of waiting removed every day, often worth far more than the minutes saved on the bill.

i
Tip

Measure the duration of each job and step over time. A pipeline that slowly drifts from five minutes to twenty teaches developers to avoid it, and they start batching changes, which brings back the big risky releases that automation was meant to end.

15Reliable Releases: Strategies, Rollback, and Resilience

Every release carries risk. The goal is not zero risk, which is impossible, but a release process where mistakes are small, visible, and quickly reversible.

Deployment strategies

StrategyHow it worksStrengthCost or catch
In-place (recreate)Stop the old version, start the newSimpleDowntime during the switch
RollingReplace instances a few at a timeNo downtime, little extra capacityTwo versions run together briefly; slower rollback
Blue-greenRun old (blue) and new (green) side by side, switch traffic at onceInstant rollback by switching backDouble capacity during the switch
CanarySend a small share of users to the new version first, then growReal-traffic test with limited damageNeeds good monitoring and traffic control
Feature flagsShip code switched off, then enable for chosen usersSeparates deployment from releaseFlags must be cleaned up and tested
Analogy

A canary release borrows its name from the canary that coal miners carried to detect bad air. Before sending every customer into the new version, a few go in first. If the canary shows distress, in the form of rising errors or slow responses, you pull everyone back before real harm is done.

Health checks as gates

A deployment is not finished when files are copied. It is finished when the new version proves it is healthy. Good pipelines run smoke tests (quick checks that the main paths work), query monitoring for error rates and response times for several minutes after release, and only then declare success. If checks fail, the pipeline triggers rollback automatically or alerts the on-call engineer. Azure features help: App Service slot swaps with warm-up, Container Apps traffic weights, Kubernetes readiness probes, and Azure Monitor alerts that a workflow can query.

Rollback

Plan rollback before you need it. For slot-based hosting, swap back. For containers, redeploy the previous image digest, which is why you keep old images and never reuse tags. For Kubernetes, roll back to the previous revision. For infrastructure, re-apply the previous version of the code. The hardest case is database changes, because data cannot be un-written. Use the expand and contract approach: first add new columns or tables in a way that old and new code both work (expand), deploy the new code, and only much later remove the old structures (contract). That way, rolling the application back never hits an incompatible database.

Resilience of the pipeline itself

  • Retries for flaky steps. Network blips happen. Retrying a download once is sensible; retrying a test failure repeatedly can hide real bugs.
  • Idempotent deployments. Running the same deployment twice should produce the same result, so a re-run after a partial failure is safe.
  • A manual path. If GitHub has an outage on the day of a critical fix, can you deploy another way? Document an emergency procedure and test it, with extra approvals and an audit trail.
  • Multi-region awareness. For globally important apps, deploy to regions one at a time, starting with the least busy, so that a bad release never hits everywhere simultaneously.
  • Dependencies on the internet. Pipelines download actions, packages, and images. Mirror critical ones in your own registry to survive outages of public services.

Measuring delivery health

The research-based DORA metrics describe how well a team delivers software: how often it deploys, how long a change takes from commit to production (lead time), what percentage of releases cause problems (change failure rate), and how fast it recovers (time to restore). GitHub Actions run data, deployment records, and incident tickets can feed these numbers. Watch the trend, not just a single day, and use it to find the biggest bottleneck rather than to rank people.

16Monitoring, Logging, and Troubleshooting

Pipelines fail, and when they do, speed of diagnosis matters. Knowing where evidence lives and recognising the usual suspects turns hours of confusion into minutes.

Where to look

GitHub

Run logs

Each step has an expandable log. Re-running with debug logging switched on adds much more detail, and it is safe to ask for it on a failed run.

GitHub

Run summary and annotations

Errors and warnings appear on the run page, and workflows can write their own summary tables for humans.

GitHub

Audit log

Records organisation-level changes, such as who changed a secret, a runner group, or an approval rule.

Azure

Activity Log

Lists every management operation on Azure resources, including who or what performed it and whether it succeeded.

Azure

Entra sign-in logs

For service principal and workload identity sign-ins, shows failed token exchanges and the reasons for refusal.

Azure

Application Insights and Azure Monitor

Show whether the newly deployed application is healthy, with error counts, response times, and traces.

Common failures and their usual causes

What you seeUsual causeWhat to do
Login fails: no matching federated identity recordThe token’s subject does not match any federated credential, such as running on a branch when the credential expects an environmentCompare the subject in the error with the registered one; fix spelling or add a credential
Login fails: unable to get the identity tokenThe job lacks permission to request an identity tokenGrant the job the identity-token write permission
Authorization failed for an action on a resourceThe identity has no role at that scope, or the role was recently assigned and has not yet taken effectCheck the role assignment and scope; allow a few minutes for propagation
Resource provider not registeredThe subscription has never used that kind of serviceRegister the provider once, by an administrator
Quota exceeded or SKU unavailableRegional capacity or core limits reachedRequest a quota increase or pick another size or region
Deployment succeeded but site shows old versionCache, wrong slot, or the app started from a different packageCheck slot swap status, app settings, and the deployed package version
Container pull failed on the targetThe service’s identity lacks pull rights on the registry, or the tag does not existGrant the pull role and verify the image name and digest
Job waits forever for a runnerNo online runner matches the labels, or the runner group excludes the repositoryCheck runner status, labels, and group access
Cannot reach a private resourceHosted runner is outside the private network, or DNS is missing for a private endpointUse in-network runners or hosted networking, and check private DNS
Workflow does not triggerPath or branch filters exclude the change, the workflow file is invalid, or it is in the wrong folderReview triggers and the file location; check the Actions tab for parsing errors
Works on Linux, fails on WindowsPath separators, shell differences, or case sensitivitySet the shell explicitly and test on the target operating system
Rate limit or throttling errorsToo many parallel calls to Azure or to a package sourceReduce parallelism, add retry with backoff, use caches

A calm method for investigating

1

Find the first error

Scroll to the earliest failing step, not the last. Later errors are often consequences.

2

Sort the problem

Is it the code (tests fail), the pipeline (syntax, permissions, runner), the identity (login or authorization), or the target (Azure rejects the request)? Each points to a different log.

3

Compare with the last good run

Ask what changed: code, workflow file, action versions, Azure settings, role assignments, or the date, since certificates and tokens can expire.

4

Reproduce in isolation

Run the failing command locally or in a small test workflow to remove noise.

5

Fix, verify, and write it down

Change one thing, re-run, and record the cause and cure in a shared run book so the next person solves it in minutes.

Monitoring the pipeline as a product

Treat the pipeline like a service with its own health. Track success rate, median and long-tail duration, queue time waiting for runners, flaky test frequency, and spend. Alert on a sudden rise in failures or a stuck deployment. Export workflow and audit data to a central system, such as Azure Monitor or a security information platform, to keep history beyond GitHub’s retention period and to correlate pipeline events with production incidents.

i
Tip

Tag every Azure deployment with the GitHub run identifier and commit. During an incident, a responder looking at a broken resource can follow the tag straight to the run that last changed it, and from there to the person and the pull request.

17Choosing and Migrating: GitHub Actions, Azure Pipelines, and Others

There is more than one good way to automate delivery to Azure. This chapter helps you choose, and explains how to move without chaos.

flowchart TD
  Q1{"Is your code hosted on GitHub"} -->|"Yes"| Q2{"Do you depend heavily on Azure Boards, Repos, or Test Plans"}
  Q1 -->|"No, on Azure Repos"| AP["Azure Pipelines is a natural fit"]
  Q2 -->|"No"| GA["GitHub Actions is a natural fit"]
  Q2 -->|"Yes"| MIX{"Can you accept two tools"}
  MIX -->|"Yes"| BOTH["GitHub Actions for CI and Azure Boards for planning"]
  MIX -->|"No"| AP2["Evaluate Azure Pipelines with GitHub repositories"]
  GA --> Q3{"Need private network access"}
  Q3 -->|"Yes"| SELF["Add self hosted or network attached runners"]
  Q3 -->|"No"| HOST["Use GitHub hosted runners"]
Figure 17.1: A simplified guide to choosing a pipeline tool for Azure delivery

The main options compared

AspectGitHub ActionsAzure PipelinesJenkins and others
HomeInside GitHubInside Azure DevOps (can also build GitHub repositories)Self-run server, or other vendors
Pipeline formatYAML workflow filesYAML pipelines (plus older visual editors)Jenkinsfiles or vendor formats
Sharing building blocksMarketplace actions, reusable workflowsTasks, templates, extensionsPlugins
ApprovalsEnvironment reviewersEnvironments with approvals and checks, mature release gatesPlugins or manual steps
Azure sign-inOIDC federation, secretsService connections, with workload identity federation availablePlugins and credentials stores
Work tracking integrationGitHub Issues and ProjectsAzure BoardsVaries
Operation effortLow with hosted runnersLow with hosted agentsHigh: you run and patch the server
!
Watch out

Product roadmaps move. Microsoft has been investing heavily in GitHub-centred developer workflows, and both products continue to receive updates. Do not rely on a blog post, including this book, for statements about what will or will not be supported in future. Check the current announcements before committing to a long migration.

How to decide

  • Where does the code live? Keep pipelines close to the code unless there is a strong reason not to.
  • What does the organisation already run? A mature Azure DevOps estate with hundreds of release pipelines is expensive to move, so migrate gradually, if at all.
  • What are the compliance needs? Check approval, audit, and separation-of-duties features of each against your auditors’ expectations.
  • Do developers prefer one? Daily experience matters; a tool people like is used properly.
  • How strong are your self-hosting needs? If private network access is critical, compare each product’s runner and agent models.

Migrating without drama

1

Inventory

List all pipelines, triggers, secrets, service connections, schedules, and approvals. Many surprises hide in old, rarely run pipelines.

2

Pilot with one friendly service

Choose a low-risk application with an engaged team. Learn the new tool, build reusable templates, and document pitfalls.

3

Fix identity first

Create new OIDC-based identities with minimal roles, rather than copying old broad credentials across.

4

Run old and new in parallel

For a short period, run both and compare results before switching production deployments.

5

Migrate in waves and retire the old

Move teams in groups, and switch off old pipelines and delete their credentials, so that no forgotten path to production remains.

GitHub provides an importer tool that can read pipelines from Azure DevOps, Jenkins, CircleCI, and others and produce a first draft of workflow files. Treat its output as a starting point to review, since some features convert imperfectly or not at all.

18Patterns and Anti-Patterns

Teams that succeed with GitHub Actions on Azure tend to repeat a few good designs. Teams that struggle tend to repeat a few familiar mistakes. Knowing both saves months.

Patterns that work

Pattern

Build once, promote everywhere

One artifact or image digest moves through every environment. Only configuration changes between them.

Pattern

Passwordless by default

OIDC for pipelines and managed identities for applications, so there are no long-lived keys to rotate or leak.

Pattern

Environment-bound trust

Production identity trusts only jobs in the protected production environment, which requires approvals and specific branches.

Pattern

Plan on pull request, apply on merge

Infrastructure changes are previewed in review and applied only after approval, with the same reviewed code.

Pattern

Golden path templates

A platform team publishes reusable workflows with security and standards built in, and product teams simply call them.

Pattern

Progressive delivery

Slots, canaries, and health gates limit the blast radius of every release, with automatic rollback.

Pattern

GitOps for clusters

The pipeline updates a desired-state repository, and the cluster pulls the change, avoiding external cluster-admin access.

Pattern

Ephemeral runners

Each job gets a clean, short-lived machine, which removes leftovers and reduces the damage of a compromised job.

Habits compared

Healthy habits

  • Read-only default token permissions
  • Pinned, reviewed third-party actions
  • Separate identities for each environment
  • Deploy only from protected branches
  • Timeouts, caching, and path filters

Habits that hurt

  • One all-powerful secret used everywhere
  • Actions referenced by moving tags, never reviewed
  • Rebuilding the app in the production job
  • Editing production by hand “just this once”
  • Huge workflow files copied into fifty repositories

Four decisions worth recording

Teams that write down their key decisions avoid arguing about them again a year later. These short records use the common format of context, decision, and consequences. They are illustrative examples, not mandates.

ADR-1Example: Accepted

Use OIDC federation instead of client secrets. Context: a client secret in forty repositories expired on a weekend, stopping deployments, and nobody was sure where all the copies were. Decision: all new and migrated pipelines sign in with federated credentials bound to GitHub environments. Consequences: no rotation work and no stored Azure passwords; the team must learn subject claims, and one credential is needed per environment.

ADR-2Example: Accepted

Provide approved reusable workflows through a central repository. Context: each team wrote its own deployment workflow, with inconsistent security. Decision: the platform team maintains versioned reusable workflows for common application types, and organisation settings allow only approved actions. Consequences: faster onboarding and consistent controls; the platform team becomes a dependency and must keep the templates flexible enough to avoid workarounds.

ADR-3Example: Accepted

Run ephemeral self-hosted runners inside the Azure network for production deployments. Context: production databases and clusters are reachable only privately. Decision: a container-based runner pool in the hub network starts one runner per job and deletes it afterward; public repositories may not use it. Consequences: private access without opening firewalls; the team must maintain the runner image and monitor capacity.

ADR-4Example: Proposed

Adopt image signing and attestation checks before production deployment. Context: the company must show auditors that production only runs images built by the official pipeline. Decision: the pipeline attaches signed provenance to each image, and the cluster admits only verified images. Consequences: stronger supply-chain assurance; extra setup, key management, and a plan for emergency overrides.

19Best Practices and Common Mistakes

This chapter gathers the advice of the whole book into one checklist to use before launch, during reviews, and after incidents.

Best practices

1

Authenticate with OIDC

Remove stored Azure passwords. Bind trust to named environments and give each identity the smallest role at the smallest scope.

2

Default to read-only tokens

Set the repository or organisation default for the automatic GitHub token to read, and add permissions only per job.

3

Build once, deploy the same artifact

Promote by digest or checksum and keep configuration outside the package.

4

Protect production

Require reviews, restrict deployment branches, use environment approvals, and prevent self-approval.

5

Pin and govern actions

Pin to commit identifiers, allow-list sources, and update through reviewed automatic pull requests.

6

Use infrastructure as code with previews

Review what-if or plan output for every change and detect drift on a schedule.

7

Verify every deployment

Smoke tests, health checks, and a defined, rehearsed rollback for every service.

8

Keep pipelines fast and bounded

Cache, parallelise, filter by path, cancel superseded runs, and set timeouts on every job.

9

Share through reusable workflows

Centralise standards so fixes reach every team with one change.

10

Observe and document

Track success rates, durations, and spend, tag Azure resources with the run that made them, and keep a run book of known failures.

Common mistakes

MistakeConsequenceBetter approach
Giving the pipeline Owner or Contributor on the whole subscriptionA compromise gives total control of the cloudScope roles to a resource group or resource; split plan and apply identities
Registering OIDC trust on a branch onlyAnyone able to push to that branch name, or edit its rules, can deployBind to a protected environment with approvals
Using moving tags for third-party actionsA bad release reaches every pipeline at oncePin to full commit identifiers
Printing secrets for debuggingLogs leak credentials, especially after transformationDebug with non-secret values; use debug logging features
Running untrusted pull request code with secretsAttackers steal credentialsSplit workflows by trust; avoid privileged triggers on fork code
Rebuilding for productionUntested bytes reach usersPromote the tested artifact
No job timeoutsA hung job consumes hours of paid minutesSet a sensible timeout on every job
Ignoring concurrencyTwo deployments race and the older one winsQueue deployments per environment
Persistent self-hosted runners for many repositoriesCross-contamination between jobsEphemeral runners, scoped runner groups
Leaving preview environments runningSurprise Azure billsDelete on pull request close and sweep orphans nightly
Changing the database in a way old code cannot handleRollback becomes impossibleExpand and contract migrations
Copying huge workflows between repositoriesDrift and unpatched copiesReusable workflows with versioning
A pipeline is a trusted employee with the keys to the building. Give it only the keys it needs, watch what it does, and never let strangers give it orders.

20Real-World Scenarios

The following scenarios are invented illustrations built from common situations. They are not case studies of real companies, and the numbers are examples chosen to make the ideas concrete.

Scenario 1: A small team ships a web app to App Service

Situation. An imagined four-person start-up runs an appointment-booking site on one Azure App Service plan. Releases were done by uploading files from a laptop, and a mistaken upload once took the site down for an hour.

Design. The team creates one workflow. A pull request triggers build and tests. A merge to main builds the package once, uploads it as an artifact, and deploys it to a staging slot using OIDC. A smoke test calls the staging address, and then a production environment with one required reviewer swaps the slots. The deployment identity has rights only on the one web app. Previous versions stay one swap away.

Outcome. Releases take about ten minutes with two clicks. The last bad release was undone in under a minute by swapping back. No Azure password exists in GitHub.

Scenario 2: A retailer delivers microservices to AKS

Situation. An imagined retail company has 40 microservices on AKS and ten product teams. Each team used to maintain its own pipeline, and audits found inconsistent scanning and a few leaked secrets.

Design. A platform team publishes reusable workflows for build, scan, push to ACR, sign, and update a GitOps repository. Organisation settings allow only approved actions. Each service uses a pipeline identity that can push to the registry and nothing else. A cluster add-on pulls changes from the GitOps repository, so the pipeline never holds cluster-admin rights. Production changes need approval from the owning team and a platform engineer.

Outcome. New services get a compliant pipeline in an afternoon, image scanning is uniform, and the auditors can trace every production container to a commit, a run, and an approver.

Scenario 3: A bank deploys into a locked-down network

Situation. An imagined bank forbids public access to its databases, clusters, and registry. Hosted runners on the internet cannot reach them.

Design. Ephemeral runner containers start inside a hub virtual network, one per job, and are destroyed afterward. Only repositories in the production runner group may use them, and pull requests from forks never do. The registry has a private endpoint. Infrastructure changes are previewed by a read-only identity on pull requests and applied by a separate identity in a protected environment with two approvers. Logs are exported to the bank’s central monitoring system.

Outcome. Deployment works without opening any firewall to the public internet, and the security team can demonstrate separation of duties and complete audit trails.

Scenario 4: A media company adds preview environments

Situation. An imagined media company wanted editors and designers to review website changes before release. Reviewing screenshots in chat caused misunderstandings.

Design. Each pull request deploys to a temporary Static Web Apps preview address, and a comment on the pull request links to it. When the pull request closes, the preview is deleted automatically. The production site deploys only from main after the staging checks pass.

Outcome. Reviewers see real pages, feedback arrives earlier, and the cost stays small because previews are short-lived.

21Frequently Asked Questions

Short answers to the questions beginners ask most often.

Q1 What is GitHub Actions in one sentence?
It is GitHub’s built-in automation service: you describe a process in a text file in your repository, and GitHub runs it on a machine whenever an event such as a push, a pull request, or a schedule occurs.
Q2 Do I need to install anything to use it?
No. With GitHub-hosted runners there is nothing to install. You add a workflow file to your repository and GitHub does the rest. You only set up your own runner if you need private network access or special software.
Q3 How does a workflow prove to Azure who it is?
The recommended way is OpenID Connect. GitHub issues a short-lived signed token describing the job, and Microsoft Entra ID exchanges it for a short-lived Azure access token if the token matches a federated credential you created. No password is stored.
Q4 What is a service principal?
It is an identity for an application or automation in Microsoft Entra ID. Azure role assignments say what that identity may do. Think of it as an employee badge for software.
Q5 What is the difference between a service principal and a managed identity?
A managed identity is a special service principal whose credentials Azure creates, stores, and rotates for you, so you never see a secret. It is ideal for Azure-hosted resources. A regular app registration service principal is used when something outside Azure, such as GitHub, needs an identity, and with OIDC it too can avoid secrets.
Q6 Why do I still see advice to store AZURE_CREDENTIALS as a secret?
That was the standard method for years and many tutorials still show it. It still works, but it stores a long-lived secret. Prefer OIDC for new work, and check current Microsoft documentation for the recommended approach for your service.
Q7 What role should my pipeline identity have?
The smallest role that lets it do its job, assigned at the smallest scope that works, such as a resource group or a single web app. Avoid Owner, and avoid subscription-wide Contributor unless you have a clear reason.
Q8 Should I use hosted or self-hosted runners?
Start with hosted runners because they are clean, maintained, and simple. Move to self-hosted or network-attached runners only when you must reach private resources or need special software, and prefer ephemeral ones.
Q9 How do I deploy to production safely?
Use a GitHub environment named production with required reviewers and a branch restriction, bind the production Azure identity to that environment, deploy the same artifact that was tested in staging, run health checks, and have a rehearsed rollback.
Q10 What is a deployment slot?
A live copy of an App Service or Functions app with its own address. You deploy and test the new version in a staging slot, then swap it with production for a nearly instant switch and an easy way back.
Q11 Can GitHub Actions deploy infrastructure too?
Yes. It can run Bicep, ARM, Terraform, or other tools. Best practice is to preview changes on pull requests and apply them after approval in a protected environment, with separate identities for planning and applying.
Q12 Are third-party actions safe?
They are code written by others that runs inside your pipeline. Prefer official or well-maintained ones, pin them to a full commit identifier, review updates, and use organisation settings to allow only approved actions.
Q13 How can I make my workflow faster?
Cache dependencies, run independent jobs in parallel, filter triggers by file path, cancel superseded runs, order steps so cheap checks fail first, and choose an appropriate runner size.
Q14 How much does it cost?
GitHub plans include an allowance of hosted runner minutes, and extra usage is billed per minute at rates that depend on the operating system and machine size. Azure resources that your pipeline creates or uses are billed separately. Rates change, so check the current pricing pages.
Q15 Why does my deployment say authorization failed?
The identity probably lacks a role at the needed scope, the role was just assigned and has not propagated, or you are deploying to a different resource group than you granted. Check the role assignments and the Azure Activity Log.
Q16 How do I roll back a bad release?
Swap the slot back, redeploy the previous container digest, roll back the Kubernetes revision, or re-apply the previous infrastructure code. Keep old artifacts and plan database changes so that old code still works.
Q17 Should I choose GitHub Actions or Azure Pipelines?
If your code is on GitHub and you do not rely heavily on Azure Boards or existing release pipelines, GitHub Actions is usually the natural choice. If you already have a large Azure DevOps estate, staying or migrating gradually may make more sense. Both can deploy to Azure.
Q18 What is the best way to learn it?
Create a small repository with a simple web app, set up OIDC to a test resource group, build a workflow that runs tests and deploys to a staging slot, add an environment with an approval, and then deliberately break something to practise reading logs. Doing the whole loop once teaches more than reading about each piece separately.

22Summary and Key Takeaways

You began with a bakery whose recipes were carried by hand. You now have the blueprint for an automatic delivery line from GitHub to Azure.

GitHub Actions turns every code change into an automatic, repeatable journey: an event starts a workflow, jobs run on clean runners, the build is tested and stored once, and a protected deployment job signs in to Azure with a short-lived OIDC token and delivers the same tested artifact to App Service, Functions, Container Apps, AKS, or other targets. Environments, approvals, and slots or canaries make releases safe, and monitoring, tagging, and run books make problems quick to solve. Around all of this sit the disciplines that keep a pipeline trustworthy: least privilege, pinned actions, careful treatment of untrusted code, ephemeral runners, infrastructure as code with previews, and constant attention to speed and cost.

Key takeaways

  • Automate the whole path. Build, test, package, and deploy the same way every time, with a record of each run.
  • Go passwordless. Use OIDC federation for pipelines and managed identities for applications, and bind production trust to a protected environment.
  • Build once, promote the same artifact. Never rebuild for production; promote by digest and keep configuration separate.
  • Apply least privilege everywhere. Read-only workflow tokens, narrow Azure roles, separate identities per environment, and approvals for production.
  • Treat the pipeline as a target. Pin and allow-list actions, guard against untrusted pull request code, and prefer ephemeral runners.
  • Release progressively. Slots, canaries, health checks, and a rehearsed rollback make mistakes small and reversible.
  • Measure and improve. Watch duration, success rate, spend, and delivery metrics, and share good patterns through reusable workflows.
i
Where to go next

Build a small end-to-end project: a simple web app, an OIDC-based identity with a narrow role, a workflow that tests and deploys to a staging slot, and a production environment with an approval. Then read the current GitHub and Microsoft documentation for details that change quickly, such as action versions, pricing, runner options, and newly supported features.