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¶
- ✅ Creates Azure AD app registration named
github-actions-portfolio-oidc - ✅ Configures federated credentials for:
- Main branch deployments
- Pull request deployments
- Tag-based deployments
- Environment-specific deployments (dev, prod)
- ✅ Creates service principal
- ✅ Assigns Contributor role to your subscription
- ✅ 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¶
Navigate to GitHub Secrets¶
- Go to your GitHub repository:
https://github.com/SvenRelijveld1995/portfolio - Click Settings (top menu)
- In the left sidebar, click Secrets and variables → Actions
- 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¶
Option A: Test with a Pull Request (Recommended)¶
- Create a new branch:
- 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
- Create a Pull Request:
- Go to GitHub and create a PR from your branch to
main validate-infrastructure.ymlruns (ifinfra/**changed) and posts a what-if analysistest-api.ymlruns (ifapi/**ortests/**changed) and publishes test results-
deploy-pr-preview.ymldetects changed paths and only builds/deploys what changed -
Review the Results:
- Check the Actions tab for workflow runs
- Review the PR comment with dev environment URLs
-
Test the deployed application at the dev URLs
-
Merge or Close:
- Merge to
mainto trigger the production deployment - The dev environment persists (shared across PRs; no automatic cleanup)
Option B: Manual Workflow Dispatch¶
- Go to Actions tab in GitHub
- Select Validate Infrastructure workflow
- Click Run workflow
- Select branch:
main - Click Run workflow
Step 4: Deploy to Production¶
Initial Production Deployment¶
Once you've verified the workflows work correctly:
- Merge your test PR (if you created one)
- Push to main branch:
- The deployment workflow will automatically:
- Validate Bicep templates
- Run what-if analysis
- Deploy to production environment
- Create service principal for local development
-
Store SP credentials in Key Vault
-
Monitor the deployment:
- Go to Actions tab
- Click on the running workflow
- Watch the deployment progress
Verify Production Deployment¶
After deployment completes:
- Check Azure Portal:
- Resource Group:
dna-prod-portfolio-westeurope-rg -
Verify all resources are created
-
Test the URLs:
- Frontend URL (from workflow output)
-
Backend URL (from workflow output)
-
Check Application Insights:
- 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
pytestwith 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
maintouchinginfra/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:
detect-changes— classifies changed files intomain_modules,dev_params,prod_params,sharedvalidate— runs only the steps relevant to what changed:
Actions:
- Bicep lint on both
main.bicepandshared.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 toprod - 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:
- Path filters don't match changed files
- Branch protection rules blocking workflow
- 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:
- Go to Settings → Branches
- Add rule for
mainbranch - Enable:
- ✅ Require pull request reviews
- ✅ Require status checks (validation workflow)
- ✅ Require branches to be up to date
- ✅ Do not allow bypassing
2. Environment Protection¶
Configure environment protection for prod and shared:
- Go to Settings → Environments
- Click New environment → Name:
prod, then repeat forshared - For
prodenable: - ✅ Required reviewers (add yourself)
- ✅ Wait timer: 5 minutes
- ✅ Deployment branches:
mainonly - For
sharedenable: - ✅ Deployment branches:
mainonly
3. Monitoring¶
Set up monitoring for workflows:
- Email Notifications:
- Go to Settings → Notifications
-
Enable workflow failure notifications
-
Slack Integration (optional):
- Install GitHub app in Slack
- 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.ymlworkflow — deploys DNS on push tomain - [x]
validate-infrastructure.ymlextended 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.com—deploy_custom_domain=trueset inparameters.dev.bicepparam; SniEnabled managed cert active - [x]
docs.sven-relijveld.com—deploy_swa_custom_domain=trueset; cname-delegation active - [x]
dev-docs.sven-relijveld.com—deploy_swa_custom_domain=trueset; 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¶
External Links¶
Support¶
- Email: sven.relijveld@onedna.nl
- GitHub: @SvenRelijveld1995
- Repository: https://github.com/SvenRelijveld1995/portfolio
Changelog¶
Version 1.2 (February 28, 2026)¶
- Added
test-api.ymlworkflow (pytest +dorny/test-reporter) deploy-pr-preview.ymlrestructured 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