Azure Devops To Github Actions

Azure Devops To Github Actions

Reading time1 min
#DevOps#Cloud#Automation#AzureDevOps#GitHubActions#CICD

Migrating CI/CD Pipelines from Azure DevOps to GitHub Actions: A Practical Guide

Assume you’re running builds, tests, and deployments on Azure DevOps, but your source of truth is already GitHub. Maintaining two disparate platforms increases operational overhead and introduces friction in your automation lifecycle. Centralizing CI/CD in GitHub Actions isn’t just cost-efficient—it’s often inevitable as repositories, code discussions, and workflow automation converge.

A linear migration rarely pans out. Duplicate pipelines, mismatched secrets, missing permissions, subtle YAML divergences: all can halt a move if overlooked. Below is a field-tested approach developed migrating both cloud-native and legacy .NET monoliths.


Platform Differences: Azure DevOps vs GitHub Actions

Table 1. Comparative Overview

FeatureAzure DevOpsGitHub Actions
YAML PipelinesSupported, but syntax divergesSupported, more Markdown-like
Triggerstrigger, pron: with fine-grained filters
Hosted Agentswindows, ubuntu, macOSubuntu-latest, etc.
Secret ManagementService Connections & Vaultrepo/org-level Secrets
Marketplace ActionsTask-based, smaller ecosystemLarge community catalog
Permission ModelRBAC, finer-grainedSimpler GH repo permissions

Note: If you rely on Azure DevOps classic UI pipelines, migration requires a complete translation, not just a YAML lift-and-shift.


Step 1. Build a Pipeline Inventory

Skip this and you risk missing deployment triggers or test coverage. List:

  • Pipeline names and locations
  • Pipeline triggers (e.g., PR validation, nightly builds)
  • Build/test environments (windows-latest, ubuntu-20.04, or self-hosted runners?)
  • Integration points (Azure Key Vault, custom agents, artifact feeds)
  • All secrets and service principals in-use
  • State transitions (release approvals, manual interventions)
  • Non-obvious: Any use of pipeline variables set at runtime
PipelineTriggerArtifactEnvironmentNotes
build-dotnetPR, push.NET DLLubuntu-latestCustom NuGet source
deploy-stagingManual, tagzip packageAzure App ServiceNeeds Azure connection string

A spreadsheet isn’t overkill here. Gaps here equal outages later.


Step 2. Prepare GitHub Repo and Enable Actions

  1. Repository on GitHub (main branch ideally protected).
  2. Actions enabled (default: yes, but confirm under Settings > Actions).
  3. Restrict workflow permissions (Read/write triggers security reviews; consider using Read and explicitly elevate in workflow when needed).
  4. Map existing branch protection rules to GitHub. Subtle mismatch in required status checks or reviewers often blocks merges unexpectedly.

Step 3. Map Pipeline Triggers

Azure DevOps may use trigger: and pr: keys; GitHub expects an on: block. Example translation:

Azure DevOps YAML:

trigger:
  branches:
    include:
      - main
      - release/*
pr:
  branches:
    include:
      - main

GitHub Actions YAML:

on:
  push:
    branches:
      - main
      - 'release/**'
  pull_request:
    branches:
      - main

Gotcha: Patterns differ. In GitHub Actions, 'release/*' matches only one folder deep; 'release/**' necessary for all descendants.


Step 4. Migrate Build & Test Logic

Task translations rarely map 1:1. For .NET projects:

Azure DevOps:

- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '6.x'
- script: dotnet build --configuration Release

GitHub Actions:

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x'
      - run: dotnet restore
      - run: dotnet build --configuration Release --no-restore
      - run: dotnet test --no-build --verbosity normal
  • Note: actions/setup-dotnet@v3 replaces UseDotNet. Some environment variables (DOTNET_CLI_TELEMETRY_OPTOUT=1) may now need to be set explicitly.
  • NuGet authentication to private feeds: In DevOps, handled via Service Connections; in Actions, add a secret then inject it in a step:
    - name: Authenticate NuGet
      run: dotnet nuget add source --username ... --password ${{ secrets.NUGET_TOKEN }}
    

Step 5. Translate Deployment Steps

App Service deploys most often trip up migrations.

Azure DevOps:

- task: AzureWebApp@1
  inputs:
    azureSubscription: 'my-azure-svc'
    appName: 'prod-api'
    package: '$(System.DefaultWorkingDirectory)/drop/*.zip'

GitHub Actions:

- uses: azure/webapps-deploy@v2
  with:
    app-name: prod-api
    publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
    package: './drop/*.zip'
  • Obtain publish profile via Azure Portal > App Service > Get publish profile, store as AZURE_PUBLISH_PROFILE in repo secrets.

Known issue: Publish profile permission mismatches can hard-fail deploys with:

##[error]Failed to deploy web package to App Service.

If this occurs, verify publish profile scope and expiration on Azure.


Step 6. Migrate Secrets and Secure Connections

Do not port plaintext secrets. Instead:

  • Settings > Secrets and variables > Actions > New repository secret
  • Store: AZURE_PUBLISH_PROFILE, any API keys, NuGet tokens, Slack webhooks, etc.
  • Replace $(Variable) in Azure DevOps with ${{ secrets.VARIABLE }} in GitHub Actions.
Secret NameUsed For
AZURE_PUBLISH_PROFILEDeployment auth
NUGET_TOKENPrivate package feed
SLACK_WEBHOOK_URLAlert notifications

Step 7. Progressive Workflow Validation

Blind “big-bang” cutovers break production. Safer path:

  1. Fork a feature branch, commit minimum viable workflow (.github/workflows/ci.yml).
  2. Use manual triggers (workflow_dispatch) to verify build/test before PR triggers.
  3. Observe real logs under the Actions tab—errors such as:
    ##[error]The process '/usr/bin/dotnet' failed with exit code 1
    
    Usually mean path mismatches or missing secrets, not genuine code failures.
  4. Validate artifact outputs. In Azure DevOps, artifacts are explicit; in GitHub Actions, use the actions/upload-artifact and actions/download-artifact steps.
  5. Once validated, restrict Azure DevOps triggers to avoid double deployments during the interim period.

Additional Recommendations

  • For local workflow debugging, nektos/act provides limited but rapid feedback. Not all marketplace actions are fully supported—expect partial fidelity.
  • Modularize your workflow files—prefer composable jobs and use needs dependencies to isolate failures.
  • Use matrix strategies where parallel multi-version or OS testing is required (e.g., test across ubuntu-latest, windows-latest).
  • Artifact expiration defaults differ. GitHub Actions deletes artifacts after 90 days; adjust via retention policy if necessary.

Example: Minimal .NET CI/CD Workflow

name: build-and-deploy

on:
  push:
    branches:
      - main

jobs:
  build-test:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 6.0.x
      - run: dotnet restore
      - run: dotnet build --configuration Release --no-restore
      - run: dotnet test --no-build --verbosity normal
      - uses: actions/upload-artifact@v3
        with:
          name: build_output
          path: '**/*.zip'

  deploy:
    needs: build-test
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/download-artifact@v3
        with:
          name: build_output
      - name: Deploy to Azure App Service
        uses: azure/webapps-deploy@v2
        with:
          app-name: prod-api
          publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
          package: '**/*.zip'

Side Note: Artifact path globbing sometimes varies versus Azure DevOps; use exact patterns or debugging ls steps to confirm.


Final Thoughts

Successful migration from Azure DevOps to GitHub Actions depends on rigorous pipeline inventory, careful YAML translation, and phased adoption with live testing. Pay special attention to the security context—secrets and permissions—since failure modes are silent until a deployment breaks.

There are alternative tools (e.g., scripting a conversion), but in practice, hand-crafting and incrementally testing each workflow yields fewer surprises and more maintainable builds. For organizations with both monorepos and polyrepos, consider the implications on pipeline scalability and concurrency.

If your migration fails silently or deploys to the wrong app, check path casing: Azure DevOps is case-insensitive with artifacts, GitHub runners using Ubuntu are not.

Questions or migration lessons? Share specifics—edge cases matter.