Overview
GitHub Actions and Azure DevOps Pipelines both automate software delivery. They can build code, run tests, package artifacts, deploy infrastructure, deploy applications, run database migrations, perform security checks, and promote releases across environments.
The exact YAML syntax differs, but the delivery principles are similar:
- Build once from a known commit.
- Run tests before deployment.
- Publish immutable artifacts.
- Deploy infrastructure as code.
- Deploy applications using environment-specific configuration.
- Use least-privilege Azure authentication.
- Gate production with approvals and checks.
- Verify health after deployment.
- Keep secrets out of source control.
For interviews, strong answers explain pipeline stages, artifacts, environments, approvals, deployment jobs, service connections, OIDC or workload identity federation, Bicep deployment, rollback strategy, test placement, and why deployment automation must be both repeatable and observable.
Core Concepts
CI and CD
Continuous integration validates code changes frequently. Continuous delivery or deployment moves validated changes through environments.
Typical stages:
Build -> Test -> Package -> Deploy infrastructure -> Deploy application -> Verify -> Promote
CI answers:
- Does the code compile?
- Do tests pass?
- Is the artifact valid?
- Are security and quality checks acceptable?
CD answers:
- Can the artifact be safely deployed?
- Is the target environment ready?
- Did the release pass health checks?
- Can the release be rolled back?
GitHub Actions
GitHub Actions workflows live in .github/workflows. They run jobs in response to events such as pull requests, pushes, tags, schedules, or manual dispatch.
Example workflow shape:
name: build-and-deploy
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- run: dotnet restore
- run: dotnet build --configuration Release --no-restore
- run: dotnet test --configuration Release --no-build
Jobs should be small enough to understand and explicit enough to reproduce locally when needed.
Azure DevOps Pipelines
Azure DevOps YAML pipelines are made of stages, jobs, and steps. They can target environments and use service connections for Azure access.
Example shape:
trigger:
branches:
include:
- main
stages:
- stage: Build
jobs:
- job: BuildAndTest
pool:
vmImage: ubuntu-latest
steps:
- task: UseDotNet@2
inputs:
packageType: sdk
version: 9.0.x
- script: dotnet restore
- script: dotnet build --configuration Release --no-restore
- script: dotnet test --configuration Release --no-build
Azure DevOps environments can add deployment history, approvals, checks, and environment-specific controls.
Build Once, Deploy Many
A release should promote the same artifact through environments rather than rebuilding separately for dev, test, and production.
Benefits:
- The tested artifact is the deployed artifact.
- Production deployment is more predictable.
- Rollback can reference a known package.
- Supply-chain evidence is clearer.
Environment differences should come from configuration, parameters, and infrastructure settings, not from rebuilding different binaries.
Artifacts
Artifacts are versioned outputs from a pipeline run. Examples:
- Web app package.
- Container image.
- Bicep files and parameter files.
- Database migration package.
- Test results.
- Software bill of materials.
Artifacts should be immutable after publication. If an artifact must change, produce a new version.
Infrastructure Step
Infrastructure deployment often uses Bicep.
GitHub Actions example:
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy infrastructure
run: |
az deployment group create \
--resource-group rg-orders-prod \
--parameters infra/main.prod.bicepparam
Azure DevOps can use Azure CLI tasks, Azure PowerShell tasks, or ARM/Bicep deployment tasks through an Azure Resource Manager service connection.
What-If Before Apply
For production infrastructure, run what-if before applying changes:
az deployment group what-if \
--resource-group rg-orders-prod \
--parameters infra/main.prod.bicepparam
Use what-if output during approval so reviewers see the expected resource changes.
Application Deployment Step
Application deployment depends on the hosting model:
- App Service package deployment.
- Container image push and app revision update.
- Azure Functions deployment.
- AKS manifest or Helm deployment.
- Static Web App deployment.
- Database migration.
A safe app deployment should know:
- Which artifact is being deployed.
- Which environment is targeted.
- Which identity is performing the deployment.
- What health checks must pass.
- How to roll back.
Separate Build and Deploy Permissions
Build jobs usually need source and artifact permissions. Deploy jobs need Azure permissions. Keep these identities separate where practical.
Benefits:
- Pull-request validation does not need production access.
- Compromised test jobs cannot deploy.
- Production approvals can control when deployment identity is used.
- Least privilege is easier to reason about.
Environments and Approvals
Use environments or protected deployments for test, staging, and production.
Common gates:
- Manual approval for production.
- Required checks.
- Branch restrictions.
- Required reviewers.
- Change ticket validation.
- Security scan completion.
- What-if review.
Approval should be meaningful. Do not ask humans to approve blind deployments with no diff, test result, or health context.
Variables and Secrets
Use variables for non-secret configuration and secret stores for sensitive data.
Examples of variables:
- Environment name.
- Region.
- Resource group name.
- SKU.
- Feature flag.
Examples of secrets:
- API keys.
- Publish profiles.
- Client secrets.
- Connection strings.
Prefer OIDC or workload identity federation over long-lived client secrets.
Branch and Trigger Strategy
Common pattern:
- Pull request: build, unit test, lint, static analysis, Bicep build.
- Main branch: build, test, publish artifact, deploy to dev.
- Release tag or approval: promote to test or production.
Avoid deploying arbitrary branches to shared environments unless the environment is intentionally isolated.
Test Placement
Different tests belong at different stages:
- Unit tests during build.
- Static analysis and linting before artifact publication.
- Infrastructure validation before deployment.
- Integration tests after deployment to test environment.
- Smoke tests after production deployment.
- Availability or synthetic tests after traffic shift.
Do not wait until production to discover packaging or infrastructure errors.
Deployment Strategies
Common strategies:
- Direct deployment for low-risk environments.
- Slot swap for App Service.
- Rolling deployment for multiple instances.
- Canary deployment for controlled traffic exposure.
- Blue-green deployment for fast rollback.
The strategy should match the platform, risk, and rollback needs.
Database Migrations
Database changes need special care because rollback is harder than app rollback.
Best practices:
- Use backward-compatible migrations.
- Expand before contract.
- Deploy schema changes before app changes when needed.
- Avoid destructive changes in the same release.
- Backup or validate recovery for risky changes.
- Make migration jobs idempotent.
Pipelines should make database changes visible and auditable.
Pipeline Security
Secure pipeline design includes:
- OIDC or workload identity federation instead of secrets.
- Least-privilege Azure roles.
- Protected environments.
- Required reviews for production.
- Pinning trusted actions or tasks where appropriate.
- Avoiding script injection through untrusted inputs.
- Separating pull-request jobs from deployment jobs.
- Auditing service connection use.
CI/CD is part of the production attack surface.
Observability After Deployment
A deployment is not done when the command exits. Verify:
- App health endpoint.
- Availability tests.
- Error rate.
- Latency.
- Dependency failures.
- Queue backlog.
- App logs.
- Deployment annotations or release markers.
Good pipelines make release impact visible.
Common Mistakes
- Rebuilding separately for production.
- Deploying directly from a developer machine.
- Giving every pipeline production contributor rights.
- Storing Azure client secrets in repository secrets.
- Skipping tests for infrastructure changes.
- Running destructive migrations without review.
- Using one pipeline identity for every environment.
- No rollback path.
- No health verification after deployment.
- Treating YAML duplication as harmless forever.
Best Practices
- Build once and promote artifacts.
- Keep infrastructure as code beside application delivery.
- Run tests before deployment.
- Use environment gates for production.
- Use OIDC or workload identity federation.
- Separate CI validation from CD deployment.
- Deploy infrastructure before application when app depends on it.
- Use what-if for production infrastructure.
- Include smoke tests and health checks.
- Keep pipelines modular but readable.