Waqas Ahmad — Software Architect & Technical Consultant - Available USA, Europe, Global

Waqas Ahmad — Software Architect & Technical Consultant

Specializing in

Distributed Systems

.NET ArchitectureCloud-Native ArchitectureAzure Cloud EngineeringAPI ArchitectureMicroservices ArchitectureEvent-Driven ArchitectureDatabase Design & Optimization

👋 Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.

Experienced across engineering ecosystems shaped by Microsoft, the Cloud Native Computing Foundation, and the Apache Software Foundation.

Available for remote consulting (USA, Europe, Global) — flexible across EST, PST, GMT & CET.

services
Article

Azure DevOps vs GitHub Actions: Choosing CI/CD

Side-by-side comparison of Azure DevOps Pipelines and GitHub Actions: triggers, secrets, environments, and Azure deployment.

services
Read the article

Introduction

This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).

If your team is picking a CI/CD platform, you’ve probably heard both names: Azure DevOps and GitHub Actions. They both run builds, tests, and deployments from YAML; they both integrate with Azure. The real question is which one fits your organisation—where your repos live, how you already work, and what you need from approvals and secrets. I’ve seen teams stick with Azure DevOps because everything (repos, boards, pipelines, artifacts) is already there and approval gates are built in. I’ve also seen teams move to GitHub Actions because the repo is on GitHub and they want one place for code and CI. There’s no single right answer; it depends on context.

This article compares the two in enough detail that you can decide. We’ll cover what each platform actually is, how triggers and secrets work, how environments and approval gates differ, and how you deploy to Azure from both. We’ll also look at a side-by-side YAML example so you can see the same pipeline expressed in each. By the end you should know when to choose which and what to watch for if you’re migrating or evaluating.

For a deeper overview of this topic, explore the full Cloud-Native Architecture guide.

Decision Context

  • System scale: One or more repos building and deploying to Azure; from a handful of pipelines to many teams and environments. Applies when you’re choosing or standardising on a CI/CD platform (Azure DevOps or GitHub Actions).
  • Team size: One to many teams; someone must own pipeline YAML, secrets, and environments. Works when dev and ops can agree on where repos and pipelines live (Azure DevOps vs GitHub).
  • Time / budget pressure: Fits greenfield (“we’re picking a platform”) and existing (“we’re already on one, should we move?”). Breaks down when there’s no capacity to migrate—then stay put and optimise what you have.
  • Technical constraints: Repos in Azure DevOps or GitHub; Azure (or other) deployment targets; need for approval gates and secret management. Assumes you can use OIDC or a vault for Azure auth.
  • Non-goals: This article does not optimise for on-prem-only runners, non-Azure targets, or deep comparison with GitLab/Jenkins; it focuses on Azure DevOps vs GitHub Actions for Azure-centric teams.

What is CI/CD and why does the choice of platform matter?

CI/CD stands for continuous integration and continuous delivery (or deployment). In practice it means: every time someone pushes code or opens a pull request, you run a pipeline that builds, tests, and optionally deploys. The pipeline is usually defined in YAML in the repo, so changes to the pipeline are versioned with the code. The platform is the thing that runs that YAML—the runners, the secret store, the way you connect to Azure or other services. Different platforms have different strengths: how they trigger runs, how they handle secrets, how approval gates work, and how tight the Azure integration is. Choosing the wrong one isn’t fatal—you can migrate later—but it can cost time and cause friction. So it’s worth understanding both before you commit.

What is Azure DevOps?

Azure DevOps is Microsoft’s all-in-one dev platform. It includes Repos (Git repos), Boards (work items, sprints, backlogs), Pipelines (CI/CD), Artifacts (package feeds), and Test Plans. Many enterprises use it because they get repos, planning, and pipelines in a single place with a single identity and permission model. Pipelines can be defined in YAML (stored in the repo) or in the classic UI (stored in Azure DevOps). For anything beyond a quick experiment, YAML is the way to go: it’s in the repo, it’s reviewable, and it’s the same format the industry has standardised on.

The part that matters most for CI/CD is Pipelines. You create a pipeline that points at a YAML file (e.g. azure-pipelines.yml in the repo root). The pipeline runs when you push, when someone opens a PR, or when you trigger it manually. Each run has jobs and steps; steps can be scripts or built-in tasks (e.g. “Use .NET”, “Publish build artifacts”, “Deploy to Azure Web App”). What makes Azure DevOps stand out for Azure shops is the service connection: you configure a connection to your Azure subscription (or to ACR, Key Vault, etc.) once in the project, and the pipeline uses that connection at runtime. No long-lived Azure credentials in YAML; the pipeline identity is the service connection. You also get environments (e.g. dev, staging, production) with approval gates: a deployment to “production” can require one or more approvers before the job runs. That’s built in and widely used in enterprises.

What is GitHub Actions?

GitHub Actions is GitHub’s CI/CD system. Your workflow is defined in YAML (e.g. .github/workflows/build.yml) in the repo. Workflows are triggered by events: push, pull_request, schedule, or workflow_dispatch (manual). Each workflow has one or more jobs; each job runs on a runner—either a GitHub-hosted VM (Linux, Windows, macOS) or your own self-hosted runner. Steps in a job can run a script or use an action from the marketplace. Actions are reusable units (e.g. “checkout repo”, “set up .NET”, “login to Azure”, “deploy to Web App”). There’s a huge marketplace, so for most common tasks (building .NET, deploying to Azure, sending Slack messages) you’ll find an action. You compose steps and actions in YAML.

Secrets are stored in the repo or org settings and are exposed to the workflow as environment variables or inputs. They’re not printed in logs. For Azure, you typically use the Azure/login action (with a service principal or OIDC) and then Azure/webapps-deploy or similar to deploy. Environments in GitHub (e.g. production) can have protection rules: required reviewers, wait timer, or deployment branches. So you can get approval gates similar to Azure DevOps, but the UX and configuration live in GitHub. The main draw of GitHub Actions is that if your code is already on GitHub, CI is right there: same place for code, PRs, and workflows. No separate tool to log into for pipelines.

Side-by-side: triggers, secrets, and environments

Triggers. In Azure DevOps you configure triggers in the YAML (e.g. branches, paths) or in the pipeline UI. In GitHub Actions you put the event in the workflow (on: push, pull_request, etc.). Both support push, PR, schedule, and manual. GitHub’s event model is very flexible (e.g. on: issues: closed, or on: workflow_run for chaining). Azure DevOps has similar concepts (e.g. pipeline completion triggers). For most teams the difference is minor; both can do “run on push to main” and “run on PR.”

Secrets. In Azure DevOps you define variables (or variable groups) and mark them as secret; they’re available to the pipeline and not logged. You can also link an Azure Key Vault as a variable group so secrets are pulled from Key Vault at runtime. In GitHub you define secrets in the repo or org; they’re passed into the workflow and masked in logs. For Azure, both platforms support OIDC (federated identity) so you don’t need to store an Azure client secret in the pipeline at all; the pipeline identity is trusted by Azure. That’s the most secure option on either platform.

Environments and approvals. In Azure DevOps you create environments (dev, staging, production) and add approvals and checks. When a deployment job targets that environment, the run pauses until the required approvers approve. In GitHub you create environments and add protection rules (required reviewers, wait time). Same idea: production deploy doesn’t run until someone approves. Azure DevOps has had this for a long time and it’s very mature; GitHub’s version is a bit newer but works well. If approval gates are critical, both can deliver.

Deploying to Azure from both

From Azure DevOps you use a service connection (Azure Resource Manager) and then tasks like “Azure Web App deploy” or “Azure Kubernetes Service” (for AKS). The task uses the service connection to authenticate; you don’t put credentials in YAML. You can also call Azure CLI or PowerShell in a script step; the service connection can provide the identity for that. Key Vault integration is straightforward: you can reference Key Vault secrets in variable groups or fetch them in a step.

From GitHub Actions you use Azure/login (with a client secret or OIDC) to authenticate, then Azure/webapps-deploy, Azure/k8s-deploy, or Azure CLI in a step. There’s an action for most Azure deployment scenarios. The pattern is: login, then deploy. OIDC is supported so you can avoid storing a client secret in GitHub; you configure a federated credential in Azure AD and the workflow requests a short-lived token. Once you’re logged in, deploying to App Service, AKS, or Storage is a matter of picking the right action or running az commands.

So both platforms can deploy to Azure. Azure DevOps feels more “native” if you’re already in the Microsoft ecosystem (one portal, one identity). GitHub Actions feels natural if the repo is on GitHub and you want to stay in one place. The technical capability is similar; the difference is where you live day to day.

Example: same pipeline in both

Here’s a minimal “build .NET, test, publish artifact” pipeline in Azure DevOps YAML:

# azure-pipelines.yml
trigger:
  - main
pool:
  vmImage: 'ubuntu-latest'
steps:
  - task: UseDotNet@2
    inputs:
      packageType: 'sdk'
      version: '8.x'
  - script: dotnet restore && dotnet build --no-restore
    displayName: 'Build'
  - script: dotnet test --no-build --verbosity normal
    displayName: 'Test'
  - task: PublishBuildArtifacts@1
    inputs:
      path: '$(Build.SourcesDirectory)/src/MyApi/bin/Release/net8.0/publish'
      artifact: 'drop'
      publishLocation: 'Container'

The same idea in GitHub Actions:

# .github/workflows/build.yml
name: Build and Test
on:
  push:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'
      - run: dotnet restore && dotnet build --no-restore
      - run: dotnet test --no-build --verbosity normal
      - uses: actions/upload-artifact@v4
        with:
          name: drop
          path: src/MyApi/bin/Release/net8.0/publish

Same steps: checkout, .NET, build, test, publish artifact. The syntax differs (tasks vs actions, different naming) but the flow is the same.

Deploy to Azure Web App: Azure DevOps vs GitHub Actions

Azure DevOps: Use AzureWebApp@1 task with a service connection. GitHub Actions: Use azure/login@v1 then azure/webapps-deploy@v2. Both authenticate via service connection (Azure DevOps) or Azure/login (GitHub); prefer OIDC so no client secret is stored.

OIDC: deploy to Azure without storing secrets

OIDC (OpenID Connect) lets the pipeline authenticate to Azure without storing a client secret. The pipeline identity is trusted by Azure AD via a federated credential; the pipeline requests a short-lived token at runtime. Both platforms support it: configure a federated credential in Azure AD (subject: repo:org/repo:ref:refs/heads/main for GitHub) and use az login --federated-token or the Azure/login action with client-id, tenant-id, subscription-id (no secret).

When to choose Azure DevOps

Choose Azure DevOps when your organisation is already using it for repos and boards and you want pipelines in the same place. It’s also a strong fit when you need deep Azure integration out of the box: service connections, Key Vault variable groups, and approval gates that are well understood by ops and auditors. If you’re in an enterprise that standardises on Microsoft tooling, Azure DevOps is often the default. Migrating away from it is possible but non-trivial if you have a lot of pipelines and variable groups; so if you’re already there and it’s working, staying is often the path of least resistance.

When to choose GitHub Actions

Choose GitHub Actions when your repos are on GitHub and you want CI in the same place as your code and PRs. It’s a great fit for open source and for teams that are GitHub-centric. The marketplace of actions is large, and the event model (push, PR, schedule, workflow_dispatch, and more) is flexible. If you’re starting greenfield and the team prefers GitHub, GitHub Actions is a natural choice. It also works well for Azure: Azure/login and the deploy actions are maintained and widely used. The main gap compared to Azure DevOps is that “everything in one place” (repos, boards, pipelines, artifacts) is split if you use GitHub for code and something else for boards; but if you’re fine with that, GitHub Actions is very capable.

Enterprise best practices

Use OIDC for Azure auth. Require approval gates for production. Store secrets in Key Vault; reference from pipelines. Use self-hosted runners for internal resources. Version pipeline YAML in the repo. Use path filters to avoid unnecessary runs. Document pipelines.

Common pitfalls

Storing secrets in YAML. Never put passwords or connection strings in the YAML file. Use the platform’s secret store (variables in Azure DevOps, secrets in GitHub) and reference them in the pipeline. Prefer OIDC for Azure so you don’t need a client secret at all.

Skipping approval gates for production. Both platforms support environments with required reviewers. Use them for production (and optionally staging) so that a deploy doesn’t run without a human (or a controlled process) approving.

Assuming the same syntax. Azure DevOps tasks and GitHub Actions are not interchangeable. When you copy examples, make sure they’re for the platform you’re using. A lot of “how do I do X in GitHub Actions” searches will give you the right action and syntax.

Ignoring runner limits. GitHub-hosted runners have limits (e.g. minutes per month on free tiers); Azure DevOps has parallel job limits per tier. If you have a lot of pipelines or long-running jobs, check the pricing and limits so you’re not surprised.

Migration and coexistence

You can run both: some teams keep legacy pipelines in Azure DevOps and put new repos or new pipelines in GitHub Actions. There’s no requirement to pick one forever. If you migrate from Azure DevOps to GitHub Actions, you’ll need to rewrite YAML (task names and syntax differ), recreate secrets and variables in GitHub, and reconfigure environments and approval gates. The logic (build, test, deploy) is the same; the plumbing changes. Document your current pipelines and then recreate them in the new platform; test in a dev repo before cutting over. If you migrate the other way (GitHub to Azure DevOps), same idea: translate the workflow YAML into pipeline YAML and move secrets and environments.

You can also explore more patterns in the Cloud-Native Architecture resource page.

Summary

Both Azure DevOps and GitHub Actions can run builds, tests, and deployments from YAML and deploy to Azure. The choice usually comes down to where your repos live and what your organisation already uses: Azure DevOps when you want repos, boards, and pipelines in one place with strong Azure integration; GitHub Actions when the repo is on GitHub and you want CI next to the code. Use OIDC for Azure auth, approval gates for production, and avoid storing secrets in YAML.

Position & Rationale

I don’t treat this as a “best platform” decision—I treat it as a “best fit for this org” decision. If the org is already on Azure DevOps and pipelines are working, I recommend staying unless there’s a strong reason to move (e.g. company standardising on GitHub). If repos are on GitHub and the team is GitHub-centric, I recommend GitHub Actions so code and CI live in one place. I avoid recommending a platform switch for tooling preference alone; migration cost (YAML rewrite, secrets, environments) is real. I only push for GitHub Actions when the repo is already on GitHub and there’s no existing Azure DevOps investment, or when the business has decided to consolidate on GitHub.

Trade-Offs & Failure Modes

Staying on Azure DevOps gives you one place for repos, boards, pipelines, and artifacts and mature approval gates; you sacrifice the “code and CI in one UI” story if developers live in GitHub elsewhere. Choosing GitHub Actions gives you that single place when code is on GitHub and a large action ecosystem; you sacrifice the all-in-one Microsoft stack and may need to coordinate boards/artifacts elsewhere. Migrating between them costs YAML rewrites, secret and environment re-setup, and testing; the gain is only worth it if the target platform clearly fits the org’s direction. Failure modes: picking the “wrong” platform because of a blog post rather than repo location and org policy; skipping OIDC and storing client secrets in pipelines; or migrating without documenting and testing, leading to broken production deploys.

What Most Guides Miss

Most comparisons list features but don’t stress that where the repo lives and who already owns pipelines matter more than feature parity. Approval gates, secrets, and Azure deploy exist on both; the real differentiator is organisational fit. Another gap: OIDC for Azure is the right default for both platforms, but many tutorials still show client-id + client-secret in variables. Finally, coexistence is underplayed—teams can run some pipelines in Azure DevOps and others in GitHub Actions during a transition; you don’t have to flip everything at once.

Decision Framework

  • If repos are on Azure DevOps and pipelines are already there and working → Stay on Azure DevOps unless there’s a directive to move.
  • If repos are on GitHub and you’re greenfield or standardising on GitHub → Use GitHub Actions; put workflows in the repo and use Azure/login (prefer OIDC).
  • If you’re being asked to “pick the best” with no repo constraint → Prefer the platform that matches where the majority of repos will live and where the team already works.
  • If migrating → Document current pipelines and secrets; rewrite YAML for the target platform; recreate environments and approval gates; test in a non-production repo first.
  • For production → Use approval gates and OIDC (or a vault) on either platform; never store Azure credentials in YAML.

Key Takeaways

  • The choice between Azure DevOps and GitHub Actions is driven by where your repos live and org policy, not by a generic “which is better.”
  • Both support YAML pipelines, secrets, environments, approval gates, and Azure deployment; OIDC for Azure is the preferred auth on both.
  • Stay on Azure DevOps if you’re already there and it’s working; choose GitHub Actions if you’re GitHub-centric and want CI with the code.
  • Migration is feasible but has real cost; only migrate when the target platform clearly fits the organisation’s direction.
  • Use approval gates for production and avoid storing secrets in pipeline YAML on either platform.

For production-grade Azure systems, I offer consulting on cloud architecture, scalability, and cloud-native platform design.

When I Would Use This Again — and When I Wouldn’t

I’d use Azure DevOps again when the organisation is already on it, repos and boards are there, and the ask is to add or improve pipelines—no reason to introduce a second platform. I’d use GitHub Actions again when the repo is on GitHub, the team works in GitHub daily, and we want CI in the same place as PRs and code. I wouldn’t recommend a migration unless there’s a clear org mandate or the current platform is causing real pain (e.g. no one maintains the Azure DevOps pipelines). I also wouldn’t treat feature lists as the deciding factor; fit and continuity usually matter more.

services
Frequently Asked Questions

Frequently Asked Questions

What are Azure DevOps Pipelines?

Azure DevOps Pipelines are the CI/CD engine inside Azure DevOps. You define a pipeline (usually in YAML in the repo) that runs on push, PR, or schedule. It can build, test, publish artifacts, and deploy. Pipelines integrate with Azure via service connections and with Key Vault; they support environments with approval gates.

What are GitHub Actions?

GitHub Actions are workflows (YAML in .github/workflows/) that run in response to events (push, PR, schedule, manual). They run on GitHub-hosted or self-hosted runners. You compose steps and use actions from the marketplace (e.g. Azure/login, Azure/webapps-deploy) to build, test, and deploy.

Can I deploy to Azure from GitHub Actions?

Yes. Use the Azure/login action to authenticate (with a service principal or OIDC), then Azure/webapps-deploy for App Service, Azure/k8s-deploy for AKS, or Azure CLI for other resources. OIDC is supported so you don’t need to store a client secret in GitHub.

How do approval gates work in Azure DevOps?

You create an environment (e.g. production) in the pipeline settings and add approvals and checks. When a deployment job uses that environment, the run pauses until the required approvers approve. Approvers get a notification and can approve or reject from the Azure DevOps UI or email.

How do approval gates work in GitHub Actions?

You create an environment (e.g. production) in the repo settings and add protection rules: required reviewers, wait timer, or deployment branches. When a job targets that environment, GitHub enforces the rules (e.g. waits for reviewer approval) before the job runs.

What is a service connection in Azure DevOps?

A service connection is a stored connection to an external service (e.g. Azure subscription, ACR, GitHub). The pipeline uses it at runtime to authenticate without you putting credentials in YAML. You create it once in the project; pipelines reference it by name.

What is OIDC for Azure and why use it?

OIDC (OpenID Connect) lets the pipeline authenticate to Azure without storing a client secret. The pipeline identity is trusted by Azure AD via a federated credential; the pipeline requests a short-lived token at runtime. Both Azure DevOps and GitHub Actions support this for Azure; it’s the most secure option.

Can I use the same YAML for both platforms?

No. The syntax differs: Azure DevOps uses task and task names (e.g. UseDotNet@2); GitHub Actions uses uses and action references (e.g. actions/setup-dotnet@v4). The flow (build, test, deploy) can be the same; you write two YAML files or maintain one and transform it.

Where should I store secrets?

In Azure DevOps: Variables (or variable groups) marked as secret, or a variable group linked to Azure Key Vault. In GitHub: Secrets in the repo or org. Never put secrets in the YAML file or in code.

How do I choose between Azure DevOps and GitHub Actions?

Choose Azure DevOps if your org already uses it or you need everything (repos, boards, pipelines) in one place with strong Azure integration. Choose GitHub Actions if your repo is on GitHub and you want CI in the same place as your code. Both can deploy to Azure; the choice is often about where the repo lives and org policy.

What are GitHub-hosted vs self-hosted runners?

GitHub-hosted runners are VMs (Linux, Windows, macOS) provided by GitHub; you don’t manage them. Self-hosted runners are machines you register with GitHub; they run in your network. Use self-hosted when you need access to internal resources or want to avoid runner minute limits.

How do I run a pipeline manually?

In Azure DevOps: trigger the pipeline from the Pipelines UI and run it. In GitHub Actions: use workflow_dispatch in the on: section and trigger the workflow from the Actions tab.

Can I have multiple workflows or pipelines per repo?

Yes. In Azure DevOps you can have multiple YAML files and create multiple pipelines pointing at them. In GitHub you can have multiple workflow files in .github/workflows/. Each runs independently based on its triggers.

What is the difference between Azure DevOps and GitHub Actions for Azure deployment?

Both can deploy to App Service, AKS, and other Azure services. Azure DevOps uses service connections and built-in tasks (e.g. “Azure Web App deploy”). GitHub Actions uses Azure/login plus marketplace actions (e.g. Azure/webapps-deploy). The result is similar; the configuration and where you manage it differ.

How do I migrate from Azure DevOps Pipelines to GitHub Actions?

Rewrite the pipeline YAML to use GitHub Actions syntax (jobs, steps, uses). Recreate secrets and variables in GitHub. Recreate environments and protection rules. Test in a dev repo first. Document the current pipelines so nothing is missed

.

services
Related Guides & Resources

services
Related services