Introduction to the Migration
Migrating CI/CD pipelines from Azure DevOps to GitHub Actions is a common strategic move for organizations seeking a unified development and deployment platform. This article details a practical approach to this migration, focusing on establishing secure authentication to Azure resources using OpenID Connect (OIDC) and leveraging Azure Container Registry (ACR) for Docker image management. The exercise involves building a Go application, creating a GitHub Actions workflow, authenticating securely to Azure, building a Docker image, and pushing it to ACR. While full deployment to Kubernetes (AKS) is a subsequent step, this guide stops at ACR to highlight the core authentication and containerization aspects of the migration.
The project repository for this example can be found at github.com/Desmondgoldsmith/REST-go-k8s-example. This practical setup serves as a solid foundation before tackling a real-world application.

Core Migration Goals
The primary objectives for this migration exercise are to establish a robust CI/CD workflow in GitHub Actions that mirrors essential Azure DevOps pipeline functionalities. Specifically, the workflow should:
- Trigger automatically upon the creation of a Pull Request (PR).
- Compile and execute unit tests for the Go application.
- Build a Docker image containing the application.
- Push the built Docker image to Azure Container Registry (ACR).
This approach ensures that code changes are validated early in the development cycle and that containerized artifacts are consistently built and stored in a secure, centralized registry.
Secure Authentication with Azure OIDC
A critical component of migrating CI/CD to GitHub Actions involves securely accessing cloud resources, such as Azure Container Registry. Traditional methods often involve storing service principal credentials directly in GitHub secrets, which poses a security risk. Azure OIDC offers a more secure alternative by allowing GitHub Actions to obtain short-lived Azure credentials without exposing secrets.
The process begins by configuring an OIDC application integration within Azure AD. This involves registering GitHub as an identity provider. In GitHub Actions, this is managed through the azure/login action. This action utilizes the OIDC token issued by GitHub to request an Azure service principal token. This token is then used to authenticate subsequent Azure CLI commands or other Azure SDK operations within the workflow.
To set this up, you first need to create an Azure AD application registration and grant it appropriate permissions (e.g., AcrPush role on your ACR instance). Then, you configure a federated identity credential for this application registration, pointing to your GitHub repository and the specific branch or tag that will trigger the OIDC flow. In your GitHub Actions workflow YAML, you'll use the azure/login action, specifying your Azure Tenant ID, Client ID (of the Azure AD application), and optionally, the Subscription ID. The action handles the OIDC token exchange automatically, providing authenticated credentials for subsequent steps.
Building and Pushing Docker Images to ACR
Once authenticated to Azure via OIDC, the next step is to build the Docker image for the Go application and push it to Azure Container Registry. This is a standard practice for containerized deployments.
The workflow uses the docker/build-push-action. This action simplifies the Docker build and push process. It requires the Dockerfile path, the image name, and the tags to apply. Crucially, when used in conjunction with the authenticated Azure session established by the azure/login action, it can directly push to a private ACR instance without needing separate ACR credentials configured as GitHub secrets.
The build process typically involves defining a Dockerfile that specifies the base image (e.g., `golang:alpine` for building, and a minimal runtime image like `alpine` for the final stage), copying the application source code, building the Go binary, and setting the entry point. For the push operation, the docker/build-push-action will utilize the authenticated context to log in to the specified ACR and push the image. Tags can include commit SHAs, branch names, or semantic version numbers, enabling effective image versioning and traceability.
Workflow Structure and Triggers
The GitHub Actions workflow is defined in a YAML file within the repository's .github/workflows/ directory. For this migration scenario, a typical workflow might be triggered on two main events:
- Pull Request Creation:
on: pull_request: types: [opened, synchronize]. This ensures that code changes are built and tested automatically whenever a PR is opened or new commits are pushed to it. This is crucial for maintaining code quality and catching integration issues early. - Main Branch Push:
on: push: branches: [main]. This trigger allows for deployment-related actions, such as building and pushing the final Docker image to ACR, when changes are merged into the main branch.
The workflow will consist of several jobs:
- Build and Test Job: This job runs on a suitable runner (e.g., `ubuntu-latest`). It checks out the code, sets up the Go environment, installs dependencies, builds the application, and runs unit tests.
- Docker Build and Push Job: This job depends on the successful completion of the Build and Test job. It first logs into Azure using OIDC, then uses the
docker/build-push-actionto build the Docker image and push it to ACR. This job should also be configured to run only on pushes to the main branch.
This structure ensures that only tested code proceeds to the artifact creation stage, and that production-ready artifacts are pushed only when changes are merged into the primary development branch.
Considerations for Real-World Applications
While this exercise provides a solid foundation, migrating a real-world application involves additional considerations:
- Environment Management: Handling different environments (dev, staging, prod) will require more sophisticated workflow configurations, potentially using environment secrets and different ACR repositories or tags.
- Deployment Strategies: Integrating with Kubernetes (AKS) or other deployment targets will involve additional steps and actions within the workflow.
- Secrets Management: While OIDC handles Azure authentication, other secrets (API keys, database credentials) will still need secure management, likely using GitHub Secrets and potentially Azure Key Vault integration.
- Pipeline Complexity: Larger applications may require breaking down the workflow into multiple jobs and stages, managing dependencies, and optimizing build times.
- Rollback Strategies: Planning for rollback mechanisms in case of deployment failures is essential for production systems.
The surprising detail here is not the complexity of setting up OIDC, which is becoming increasingly streamlined, but the shift in mindset required to manage secrets and authentication securely when moving from a platform-centric model (Azure DevOps) to a code-centric model (GitHub Actions). Developers must now think about security configurations as part of their repository's codebase.
Conclusion
Migrating CI/CD from Azure DevOps to GitHub Actions with Azure OIDC and ACR is a feasible and secure approach. By leveraging OIDC, organizations can eliminate the need to store long-lived Azure credentials in GitHub, significantly enhancing security. The docker/build-push-action, combined with OIDC authentication, simplifies the process of building and pushing container images to ACR. This migration paves the way for a more integrated developer experience, especially for teams already heavily invested in the GitHub ecosystem. The next logical step for any team undertaking this migration is to map their existing Azure DevOps pipeline tasks to equivalent GitHub Actions, ensuring all necessary build, test, and deployment stages are covered.
