Skip to content

GitHub Actions Setup Guide

Version: 1.2 Last Updated: February 28, 2026


Overview

This guide walks you through setting up GitHub Actions for automated deployment of your portfolio infrastructure to Azure using OIDC authentication.


Prerequisites

Before you begin, ensure you have:

  • ✅ Azure subscription with Contributor access
  • ✅ Azure AD permissions to create app registrations
  • ✅ Azure CLI installed locally
  • ✅ Admin access to GitHub repository
  • ✅ Git installed locally

Step 1: Azure OIDC Configuration

Run the Setup Script

The setup script will create an Azure AD app registration and configure federated credentials for GitHub Actions.

# Navigate to the scripts directory
cd infra/scripts

# Make the script executable (Linux/macOS)
chmod +x setup-oidc.sh

# Run the setup script
./setup-oidc.sh

For Windows users, run the script using Git Bash or WSL.

What the Script Does

  1. ✅ Creates Azure AD app registration named github-actions-portfolio-oidc
  2. ✅ Configures federated credentials for:
  3. Main branch deployments
  4. Pull request deployments
  5. Tag-based deployments
  6. Environment-specific deployments (dev, prod)
  7. ✅ Creates service principal
  8. ✅ Assigns Contributor role to your subscription
  9. ✅ Outputs the required GitHub Secrets

Expected Output

========================================
OIDC Setup Complete!
========================================

Add these secrets to GitHub:
Repository: SvenRelijveld1995/portfolio
Settings → Secrets and variables → Actions

AZURE_CLIENT_ID: 12345678-1234-1234-1234-123456789abc
AZURE_TENANT_ID: 87654321-4321-4321-4321-cba987654321
AZURE_SUBSCRIPTION_ID: abcdef12-3456-7890-abcd-ef1234567890

Important: Copy these values - you'll need them in the next step!


Step 2: Configure GitHub Secrets

  1. Go to your GitHub repository: https://github.com/SvenRelijveld1995/portfolio
  2. Click Settings (top menu)
  3. In the left sidebar, click Secrets and variablesActions
  4. Click New repository secret

Add the Three Required Secrets

Add each of these secrets one by one:

Secret 1: AZURE_CLIENT_ID

  • Name: AZURE_CLIENT_ID
  • Value: The Client ID from the setup script output
  • Click Add secret

Secret 2: AZURE_TENANT_ID

  • Name: AZURE_TENANT_ID
  • Value: The Tenant ID from the setup script output
  • Click Add secret

Secret 3: AZURE_SUBSCRIPTION_ID

  • Name: AZURE_SUBSCRIPTION_ID
  • Value: Your Azure subscription ID
  • Click Add secret

Verify Secrets

After adding all three secrets, you should see:

AZURE_CLIENT_ID         Updated X minutes ago
AZURE_TENANT_ID         Updated X minutes ago
AZURE_SUBSCRIPTION_ID   Updated X minutes ago

Step 3: Test the Workflows

  1. Create a new branch:
git checkout -b test/github-actions-setup
  1. Make a small change (e.g., update a comment in infra/main.bicep):
# Edit infra/main.bicep
git add infra/main.bicep
git commit -m "test: verify GitHub Actions setup"
git push origin test/github-actions-setup
  1. Create a Pull Request:
  2. Go to GitHub and create a PR from your branch to main
  3. validate-infrastructure.yml runs (if infra/** changed) and posts a what-if analysis
  4. test-api.yml runs (if api/** or tests/** changed) and publishes test results
  5. deploy-pr-preview.yml detects changed paths and only builds/deploys what changed

  6. Review the Results:

  7. Check the Actions tab for workflow runs
  8. Review the PR comment with dev environment URLs
  9. Test the deployed application at the dev URLs

  10. Merge or Close:

  11. Merge to main to trigger the production deployment
  12. The dev environment persists (shared across PRs; no automatic cleanup)

Option B: Manual Workflow Dispatch

  1. Go to Actions tab in GitHub
  2. Select Validate Infrastructure workflow
  3. Click Run workflow
  4. Select branch: main
  5. Click Run workflow

Step 4: Deploy to Production

Initial Production Deployment

Once you've verified the workflows work correctly:

  1. Merge your test PR (if you created one)
  2. Push to main branch:
git checkout main
git pull origin main
  1. The deployment workflow will automatically:
  2. Validate Bicep templates
  3. Run what-if analysis
  4. Deploy to production environment
  5. Create service principal for local development
  6. Store SP credentials in Key Vault

  7. Monitor the deployment:

  8. Go to Actions tab
  9. Click on the running workflow
  10. Watch the deployment progress

Verify Production Deployment

After deployment completes:

  1. Check Azure Portal:
  2. Resource Group: dna-prod-portfolio-westeurope-rg
  3. Verify all resources are created

  4. Test the URLs:

  5. Frontend URL (from workflow output)
  6. Backend URL (from workflow output)

  7. Check Application Insights:

  8. Verify telemetry is being collected

Workflow Triggers

API Test Workflow

File: .github/workflows/test-api.yml

Triggers:

  • Pull requests affecting api/**, tests/**, or the workflow file
  • Manual dispatch

Actions:

  • Installs Python 3.11 + dependencies
  • Runs pytest with JUnit XML output (mocked SMTP — no server needed)
  • Publishes test results as a GitHub check via dorny/test-reporter

Shared Infrastructure Workflow

File: .github/workflows/deploy-infrastructure-shared.yml

Triggers:

  • Push to main touching infra/shared.bicep, infra/modules/dns_zone.bicep, infra/parameters.shared.bicepparam
  • Manual dispatch

Actions:

  • Discovers prod Container Apps Environment verification ID and frontend FQDN at runtime (az rest)
  • Discovers dev Container Apps Environment verification ID, dev frontend FQDN, prod SWA hostname, and dev SWA hostname at runtime
  • Deploys DNS zone with all conditional records: TXT (asuid, asuid.www, asuid.dev) and CNAME (www, dev, docs, dev-docs) — only created when the corresponding discovered value is non-empty
  • App Service Domain purchase (opt-in via purchase_domain: true); NS delegation automatic

Validation Workflow

File: .github/workflows/validate-infrastructure.yml

Triggers:

  • Pull requests affecting infra/** or the workflow file
  • Manual dispatch (validates all environments)

Jobs:

  1. detect-changes — classifies changed files into main_modules, dev_params, prod_params, shared
  2. validate — runs only the steps relevant to what changed:

Actions:

  • Bicep lint on both main.bicep and shared.bicep (always)
  • Template validation per environment (only if that environment's files changed)
  • What-if analysis per environment (only if that environment's files changed)
  • Dev what-if → parameters.dev.bicepparam
  • Prod what-if → parameters.prod.bicepparam
  • Shared what-if → parameters.shared.bicepparam
  • Security scan via Checkov (framework: bicep) — real IaC security analysis; findings uploaded to the GitHub Security tab as SARIF
  • Posts validation summary as PR comment, with per-environment status and a link to the Security tab

Deployment Workflow

File: .github/workflows/deploy-infrastructure.yml

Triggers:

  • Pull Request → Deploys infra to dev (no containers)
  • Merge to main → Deploys to prod (no containers)
  • Git tag (e.g., v1.0.0) → Deploys to prod
  • Manual dispatch → Choose environment

Actions:

  • Validates templates
  • Deploys infrastructure
  • Creates managed identities and RBAC role assignments unconditionally
  • Posts deployment details as PR comment

Troubleshooting

Issue: OIDC Authentication Fails

Error: AADSTS70021: No matching federated identity record found

Solution:

# Verify federated credentials
az ad app federated-credential list --id <CLIENT_ID>

# If missing, re-run setup script
cd infra/scripts
./setup-oidc.sh

Issue: Permission Denied

Error: AuthorizationFailed: The client does not have authorization

Solution:

# Verify role assignment
az role assignment list --assignee <CLIENT_ID>

# If missing, assign Contributor role
az role assignment create \
  --assignee <CLIENT_ID> \
  --role Contributor \
  --scope /subscriptions/<SUBSCRIPTION_ID>

Issue: Workflow Not Triggering

Possible Causes:

  1. Path filters don't match changed files
  2. Branch protection rules blocking workflow
  3. Workflow file has syntax errors

Solution:

# Validate workflow syntax
cat .github/workflows/deploy-infrastructure.yml | grep -A 5 "on:"

# Check workflow runs
# Go to Actions tab → All workflows → Check for errors

Issue: Staging Environment Not Cleaned Up

Error: Resource group still exists after PR close

Solution:

# Manually delete resource group
az group delete \
  --name onedna-staging-pr-<NUMBER>-portfolio-westeurope-rg \
  --yes --no-wait

Best Practices

1. Branch Protection

Configure branch protection for main:

  1. Go to SettingsBranches
  2. Add rule for main branch
  3. Enable:
  4. ✅ Require pull request reviews
  5. ✅ Require status checks (validation workflow)
  6. ✅ Require branches to be up to date
  7. ✅ Do not allow bypassing

2. Environment Protection

Configure environment protection for prod and shared:

  1. Go to SettingsEnvironments
  2. Click New environment → Name: prod, then repeat for shared
  3. For prod enable:
  4. ✅ Required reviewers (add yourself)
  5. ✅ Wait timer: 5 minutes
  6. ✅ Deployment branches: main only
  7. For shared enable:
  8. ✅ Deployment branches: main only

3. Monitoring

Set up monitoring for workflows:

  1. Email Notifications:
  2. Go to SettingsNotifications
  3. Enable workflow failure notifications

  4. Slack Integration (optional):

  5. Install GitHub app in Slack
  6. Subscribe to workflow notifications

4. Regular Maintenance

  • 🔄 Review workflow runs weekly
  • 🔄 Update GitHub Actions versions monthly
  • 🔄 Rotate service principal credentials quarterly
  • 🔄 Review Azure costs monthly

Current Status

Phase 1: Infrastructure

  • [x] OIDC authentication setup
  • [x] Validation workflow (validate-infrastructure.yml)
  • [x] Infrastructure deployment workflow (deploy-infrastructure.yml)
  • [x] Dev and prod environments

Phase 2: Application Deployment

  • [x] Build Docker images in pipeline
  • [x] Push to Azure Container Registry
  • [x] Deploy Container Apps automatically (deploy-application.yml)
  • [x] PR preview workflow (deploy-pr-preview.yml) — path-aware, skips unchanged services
  • [x] Smoke tests on production deploy

Phase 3: Quality & Testing

  • [x] API endpoint tests with pytest (test-api.yml)
  • [x] Test results published as GitHub check via dorny/test-reporter
  • [x] Path-filtered workflows (no rebuilds for unrelated changes)

Phase 4: Custom Domain / Shared Infrastructure

  • [x] Shared Bicep template (shared.bicep) for DNS zone
  • [x] deploy-infrastructure-shared.yml workflow — deploys DNS on push to main
  • [x] validate-infrastructure.yml extended with Shared template validation
  • [x] Name server delegation — automatic via Azure App Service Domain purchase
  • [x] www.sven-relijveld.com — custom domain bound, managed TLS cert active
  • [x] DNS records for dev, docs, dev-docs subdomains added to dns_zone.bicep
  • [x] dev.sven-relijveld.comdeploy_custom_domain=true set in parameters.dev.bicepparam; SniEnabled managed cert active
  • [x] docs.sven-relijveld.comdeploy_swa_custom_domain=true set; cname-delegation active
  • [x] dev-docs.sven-relijveld.comdeploy_swa_custom_domain=true set; cname-delegation active

Phase 5: Potential Enhancements

  • [ ] Automated rollback on smoke test failure
  • [ ] Security scanning (Trivy, Snyk)
  • [ ] Blue-green deployments
  • [ ] Multi-region deployment

Additional Resources

Documentation

Support


Changelog

Version 1.2 (February 28, 2026)

  • Added test-api.yml workflow (pytest + dorny/test-reporter)
  • deploy-pr-preview.yml restructured into path-aware jobs (detect-changes, deploy-app, deploy-docs, post-comment)
  • Updated workflow triggers and current status sections

Version 1.0 (February 6, 2026)

  • Initial setup guide
  • OIDC configuration instructions
  • GitHub Secrets setup
  • Workflow testing procedures
  • Troubleshooting guide