Azure DevOps → GitHub Actions
Move Azure Pipelines YAML, Classic builds, releases, variables, agents, and approvals into GitHub Actions without treating an importer pull request as a finished cutover.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Azure DevOps
- Repos, boards, pipelines, artifacts, and enterprise controls are tightly integrated
- Repository-centered workflows keep code, review, and automation close together
- The interface and administration model can feel heavy outside Microsoft-centric organizations
- Advanced governance and automation can become configuration-heavy
GitHub Actions
- Automation lives beside the repository with an enormous reusable action ecosystem
- Repository-centered workflows keep code, review, and automation close together
- Large workflows can become slow, opaque, and expensive to debug
- Advanced governance and automation can become configuration-heavy
GitHub Actions: Automation lives beside the repository with an enormous reusable action ecosystem. This removes a major source-side concern: The interface and administration model can feel heavy outside Microsoft-centric organizations.
What you lose: Repos, boards, pipelines, artifacts, and enterprise controls are tightly integrated. What you inherit: Large workflows can become slow, opaque, and expensive to debug.
Jump to a section
Know the shape of the move.
This timeline assumes
- Up to 150 pipelines, 40 shared templates, and 60 agents
- Azure DevOps Services or Server 2019 or later, because the importer needs API version 5.0.
- Repository, security, and release owners approve equivalent pipeline behavior.
- Destination workflows stay disabled until source deploy queues drain.
- The migration team records evidence for every blocking verification check.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| YAML build pipelines through Actions Importer | partial | critical | gh actions-importer can audit an organization, dry-run a pipeline, and open a pull request with converted YAML. Successful conversions still leave a Manual steps list. Partially successful pipelines convert structure but miss individual items. | Run audit, forecast, dry-run, and migrate per project. Review every pull request before merge. |
| Classic editor pipelines | manual | critical | GitHub Actions has no graphical editor. Microsoft says only Classic build designer pipelines can export to YAML, and you must be on the definition, not a run. Classic release pipelines do not support YAML export. | Export supported Classic builds to YAML first. Rewrite Classic releases as GitHub workflows by hand. |
| Secrets, service connections, and environments | lost | critical | GitHub lists organization, repository, and environment secrets, plus service connections, as manual tasks. Values must never travel inside converted YAML. | Recreate and rotate credentials as GitHub secrets or OIDC. Rebuild Environments with the same reviewers. |
| Self-hosted agents and approvals | manual | critical | Self-hosted agents, environments, and pre-deployment approvals do not convert. Pre-deployment gates, post-deployment gates, and post-deployment approvals are unsupported. Some resource triggers are unsupported. | Build labeled GitHub runners and GitHub Environments before you enable deploy jobs. |
| Unknown tasks and implicit checkout | partial | high | Unknown tasks stay unconverted. Implicit Azure DevOps checkout can appear in the audit as a GUID. GitHub documents a task-GUID lookup URL on the Azure DevOps organization. | Resolve every GUID and unknown task in the audit before you trust a green workflow. |
| Jobs, scripts, and Windows shells | partial | critical | GitHub maps script, bash, and pwsh to run plus shell. Azure Pipelines defaults Windows scripts to cmd. GitHub Actions defaults Windows to PowerShell. Azure can fail a script on stderr. GitHub does not. | Set shell: cmd where the old script needs Command Prompt. Compare exit behavior on a rehearsal branch. |
| Stages, dependsOn, and conditions | partial | critical | Azure stages do not become one GitHub workflow. GitHub asks you to split stages into separate workflow files. dependsOn becomes needs. condition functions such as eq become infix if expressions. | Draw the stage graph first. Test each condition with the same inputs you used in Azure DevOps. |
| Templates and shared steps | manual | critical | Azure template files and task groups become reusable workflows or composite actions only after you pin a target repository. The importer can write a config.yml that lists those targets. | Inventory every template. Pin reusable workflows and review GITHUB_TOKEN permissions. |
| Variable groups, schedules, and time zones | manual | high | Classic UI variables must be redefined in GitHub. Microsoft notes that YAML schedules use UTC, while Classic schedules used the organization time zone. | Rewrite every cron in UTC and test the first fire. Recreate variable groups as environments or vars. |
| Build history and job logs | lost | medium | The importer writes conversion files, not past Azure DevOps logs. Forecast reads completed runs only to estimate Actions usage. | Archive required logs and release evidence before the source becomes read-only. |
| Dual-CI deploy races | partial | critical | A converted workflow that still has deploy jobs can ship the same commit Azure DevOps is shipping. | Keep GitHub deploy jobs disabled until Azure queues drain, then switch once. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Azure Pipelines YAML job | GitHub Actions job | transform | GitHub requires explicit job structure. |
| script, bash, pwsh, or PowerShell task | run step with shell | transform | Windows defaults change from cmd to PowerShell. |
| task | Action | transform | Unknown tasks stay manual. |
| dependsOn | needs | transform | Preserve fan-out and fan-in order. |
| condition | if expression | transform | Replace eq and and with infix operators. |
| pool or demands | runs-on label | manual | Create missing self-hosted labels first. |
| Variable group or secret | GitHub variable or secret | manual | Rotate values. Do not paste them into YAML. |
| Environment and pre-deployment approval | GitHub Environment | manual | Gates do not convert. |
| Template or task group | Reusable workflow or composite action | manual | Pin the target repository from config.yml. |
Make the move recoverable.
Create the source-of-truth backup
Preserve Azure Pipelines definitions, variables, agents, and release evidence before any GitHub write.
- Export YAML pipelines and, where the definition allows it, Classic builds as YAML.
- Inventory variable groups, service connections, environments, approvals, agents, and schedules.
- Archive recent job logs, artifact names, and release evidence.
- Hash configuration exports and record pipeline IDs.
Proof to capture: A signed manifest reconciles every scoped pipeline, secret consumer, runner, and export hash.
Importer audit and forecast
See what converts, what stays manual, and how much Actions time you will burn.
- Run gh actions-importer audit azure-devops --output-dir tmp/audit.
- Read audit_summary.md for successful, partial, unsupported, and failed pipelines.
- Run forecast against recent completed jobs before you size runners.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Dry-run and dual-CI harness
Prove artifact and deploy equivalence before GitHub owns production.
- Dry-run each active build and release pipeline.
- Run the same revision on Azure DevOps and GitHub.
- Compare artifacts, tests, and deploy side effects.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
An importer pull request ships a different artifact
The workflow is green while dependencies, shells, or packaging differ.
- Consequence
- A bad build reaches users.
- Mitigation
- Compare checksums, tests, and provenance on the same commit.
Stop if: A critical artifact or test result differs.
Both systems deploy the same commit
Azure DevOps and GitHub both start a release job.
- Consequence
- Deployments race or run twice.
- Mitigation
- Disable GitHub deploy jobs until Azure queues drain, then switch once.
Stop if: Any source deployment can still start after ownership moves.
A converted workflow can reach production secrets
A pull-request job from a fork or a wide environment inherits a deploy secret.
- Consequence
- Build compromise becomes production compromise.
- Mitigation
- Use Environment protection and least-privilege secrets. Test a denied path.
Stop if: A test job can read an unauthorized secret or network.
Do the work in this order.
- Days 1–4 · inventory
Inventory and decisions
8–16 hours active3–4 days elapsedOwner review waiting- Inventory YAML, Classic, templates, agents, secrets, and environments.
- Approve which pipelines convert, rewrite, or retire.
Depends on: Azure DevOps and GitHub Actions administrator access
Stop / go checkpointExport?
Go when: Every critical pipeline has an owner and disposition.
Stop when: API version, Classic coverage, or secret ownership is unclear.
- Days 4–7 · backup
Backup and audit
6–12 hours active2–3 days elapsedAudit container run waiting- Export pipeline YAML and Classic definitions.
- Run the importer audit and reconcile pipeline IDs.
Depends on: Approved inventory
Stop / go checkpointTransform?
Go when: Audit coverage matches the inventory.
Stop when: Any production pipeline is missing from the audit.
- Days 7–24 · pilot
Map and pilot
20–70 hours active8–16 days elapsedDual-CI comparison waiting- Dry-run a quiet repository, then shared templates.
- Run Azure DevOps and GitHub on the same revisions and compare artifacts.
Depends on: Verified backup
Stop / go checkpointScale?
Go when: Pilot mappings, artifacts, access, and evidence pass.
Stop when: Any critical check fails or a GUID task is still unknown.
- Days 20–35 · cutover
Final delta and switch
6–24 hours active1–5 days elapsedQueue drain waiting- Disable Azure DevOps deploy jobs after queues drain.
- Enable GitHub as the only automation platform and run blocking checks.
Depends on: Passed pilot and approved rollback
Stop / go checkpointOpen production?
Go when: GitHub alone starts approved work.
Stop when: A source writer remains active or a blocking check fails.
- Days 22–50 · observe
Observe and close
4–20 hours active4–12 days elapsedOne release cycle waiting- Monitor one complete build and release cycle.
- Sign the verification report before you close rollback.
Depends on: Verified cutover
Stop / go checkpointClose rollback?
Go when: No trigger occurs during the agreed observation period.
Stop when: Build, deploy, or access results regress.
Cut over with a way back.
Cutover
Make GitHub Actions the only production automation system without a dual deploy.
- Drain Azure DevOps deploy queues and disable production pipelines.
- Enable the approved GitHub workflows and protected environments.
- Switch service connections, deploy keys, and release integrations to GitHub.
- Run every blocking check and keep Azure DevOps readable.
- Keep the source intact through the rollback window.
Proof to capture: GitHub Actions owns production automation, artifacts reconcile, and every blocking check has evidence.
Rollback
Return production automation to Azure DevOps without losing GitHub-era changes.
- Stop new GitHub deploy workflows.
- Restore Azure DevOps schedules, deploy jobs, and service connections.
- Export GitHub post-cutover workflow and release evidence.
- Review and apply safe destination-era changes to Azure DevOps.
- Run the same blocking checks against Azure DevOps.
Proof to capture: Azure DevOps again owns production automation with current configuration and no duplicate GitHub deploy.
- Unexplained artifact or test variance
- Missing or exposed secrets
- Duplicate production deploy
- Failed access, runner, or environment check
- A critical pipeline has no safe GitHub replacement
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Pipeline coverage | Reconcile every active Azure DevOps pipeline to a GitHub workflow or a retirement. | Every pipeline has one approved disposition. | Pipeline ledger. | |
V-02Blocking | Command and test parity | Run identical revisions and inputs on both systems. | Required tests and exit behavior match. | Run comparison. | |
V-03Blocking | Binary equivalence | Compare checksums, metadata, and retention. | Approved artifacts match. | Artifact report. | |
V-04Blocking | Event and schedule behavior | Exercise push, pull request, manual, and cron paths, including workflow_dispatch string inputs. | One correct run per approved event. | Trigger log. | |
V-05Blocking | Environment and capacity | Test tools, network, isolation, and peak concurrency. | Jobs run within approved security and time objectives. | Runner report. | |
V-06Blocking | Secrets and environments | Run allowed and denied access tests, including a pull-request job. | Least privilege and no secret disclosure. | Security evidence. | |
V-07Blocking | Approvals and deployment | Run a controlled staging and production release. | Gates, concurrency, rollback, and notifications pass. | Release evidence. | |
V-08Blocking | Single automation platform | Inspect Azure DevOps queues, schedules, and service hooks. | GitHub Actions alone starts approved work. | Cutover checklist. |
Remove the scaffolding safely.
Safe after: One complete build and release cycle, at least seven stable days, and owner sign-off on every blocking check.
- Archive Azure DevOps YAML, audit output, and verification evidence.
- Revoke migration PATs, importer tokens, and unused service connections.
- Remove obsolete Azure DevOps schedules, variable groups, and unused agents.
- Keep Azure DevOps readable through the approved retention window.
- Cancel paid Azure DevOps pipeline plans only after billing, legal, and recovery review.
- Schedule the next GitHub Actions backup, access, and playbook review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
endjin principal James Dawson wrote that the company had already moved open-source repos to GitHub while CI still ran in Azure DevOps. Sporadic trigger and status bugs across PAT, OAuth, and GitHub App connections pushed them to move CI in 2023. They waited on reusable workflows and composite actions so they could replace shared Azure DevOps templates for .NET builds. The first concrete gotcha: workflow_dispatch inputs are strings, so a boolean checkbox had to be compared to 'true' before it could be passed to a reusable workflow. The 4 May 2026 update keeps that typing note in place.
- Inventory shared Azure DevOps templates before you convert one repository, because reusable workflows are the replacement for that layer.
- When a top-level workflow_dispatch value feeds a typed reusable workflow, compare the string to 'true' instead of passing the input through.
- The move started because GitHub-to-Azure DevOps plumbing kept breaking, not because YAML would not convert.
- Reusable workflows closed the feature gap they had been waiting on.
Jamie Rees wrote that Exclaimer reviewed about 180 Azure DevOps repositories and migrated about 140. A proof of concept on a quiet UI repo imported git with GitHub's importer, then hit pipeline work. GitHub Actions Importer brought steps across but missed shared work such as installing .NET, security scanning, and version bumps, so they rewrote pipelines by hand. They replaced path reviewers with CODEOWNERS, tried Dependabot, then chose Renovate for the monorepo. A Curiosity Week team ran the cutover. After engineers returned, they reported no issues on the production codebase. The post covers repos plus CI, not Actions alone.
- Treat the Actions Importer pull request as a draft. Rewrite shared templates yourself and compare a quiet repository first.
- If you have Classic pipelines, budget a manual rewrite. Exclaimer said those could not be migrated automatically.
- The importer was useful for a first YAML shape and weak on the shared .NET steps they ran everywhere.
- They pruned unused repositories before the migration week, which cut the work from 180 to about 140.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- GitHub: migrate Azure DevOps with Actions ImporterAccessed 2026-10-25
- GitHub: migrate from Azure Pipelines by handAccessed 2026-10-25
- Microsoft: export a Classic pipeline to YAMLAccessed 2026-10-25