Skip to content

Azure Infrastructure

The infra/ directory contains Bicep templates for deploying the portfolio application to Azure using snake_case naming and CAF (Cloud Adoption Framework) naming conventions.

Naming Convention

All resources follow the CAF naming pattern:

{org}-{environment}-{project}-{region}-{component}

Example: dna-dev-portfolio-westeurope-rg

Resource Abbreviations

Resource Type Abbreviation Example
Resource Group rg dna-dev-portfolio-westeurope-rg
Key Vault kv dnadevportfoliowekv-a1b2c3 (max 24 chars)
Container Registry acr dnadevportfoliowesteuropeacr (no hyphens)
Log Analytics log dna-dev-portfolio-westeurope-log
App Insights appi dna-dev-portfolio-westeurope-appi
Container App Environment cae dna-dev-portfolio-westeurope-cae
Container App ca dna-dev-portfolio-we-ca-fe
Static Web App swa dna-dev-portfolio-westeurope-swa

See Azure Naming Validation for detailed naming rules per resource type.

Architecture

Azure Subscription
├── Resource Group (dna-shared-portfolio-westeurope-rg)   [shared.bicep]
│   ├── Azure DNS Zone (sven-relijveld.com)
│   │   ├── TXT  asuid              → CA env verification ID (prod)
│   │   ├── TXT  asuid.www          → CA env verification ID (prod)
│   │   ├── TXT  asuid.dev          → CA env verification ID (dev)
│   │   ├── CNAME www               → frontend Container App FQDN (prod)
│   │   ├── CNAME dev               → frontend Container App FQDN (dev)
│   │   ├── CNAME docs              → prod SWA default hostname
│   │   └── CNAME dev-docs          → dev SWA default hostname
│   └── App Service Domain (sven-relijveld.com)           [purchased via Azure]
└── Resource Group (dna-{env}-portfolio-{region}-rg)      [main.bicep]
    ├── Container Registry (ACR)
    ├── Key Vault (secrets)
    ├── Static Web App (MkDocs documentation)
    │   └── Custom domain binding (cname-delegation, active — docs.sven-relijveld.com / dev-docs.sven-relijveld.com)
    ├── Log Analytics Workspace
    ├── Application Insights
    └── Container Apps Environment
        ├── Managed Certificate (www.sven-relijveld.com)      [prod, SniEnabled — active]
        ├── Managed Certificate (dev.sven-relijveld.com)      [dev, SniEnabled — active]
        ├── Frontend Container App (Nginx)
        └── Backend Container App (FastAPI)

Prerequisites

  • Azure CLI installed
  • Azure subscription with Contributor access
  • Docker installed (for building images)

Quick Start

1. Set GitHub Secrets

In the repository Settings → Secrets and variables → Actions, add:

Secret Description
AZURE_CLIENT_ID App registration client ID (from OIDC setup)
AZURE_TENANT_ID Azure AD tenant ID
AZURE_SUBSCRIPTION_ID Azure subscription ID
EMAIL_ADDRESS Gmail address used by the backend contact form
EMAIL_PASSWORD Gmail app password for the address above

Email credentials are written to Key Vault automatically on every infrastructure deploy. If omitted, the backend runs in demo mode (logs email content instead of sending).

For domain purchase, also add: CONTACT_PHONE, CONTACT_ADDRESS1, CONTACT_CITY, CONTACT_STATE, CONTACT_POSTAL_CODE.

2. Deploy Infrastructure

Deployment is handled via GitHub Actions CI/CD. Trigger the infrastructure workflow manually:

# Development environment
gh workflow run deploy-infrastructure.yml --ref main --field environment=dev --field deploy_containers=false

# Production environment
gh workflow run deploy-infrastructure.yml --ref main --field environment=prod --field deploy_containers=false

Or push to main (triggers prod) / open a PR (triggers dev automatically).

3. Build and Push Docker Images

Images are built and pushed automatically by deploy-application.yml after each infrastructure deploy on main. To trigger manually:

gh workflow run deploy-application.yml --ref main --field environment=prod --field deploy_containers=true

4. Update Container Apps

Container Apps pull the latest image from ACR automatically on each application deploy.

Files

  • main.bicep — Main orchestrator template (subscription scope, per-env)
  • shared.bicep — Shared infrastructure template (subscription scope, once)
  • modules/ — Modular Bicep templates
  • key_vault.bicep — Azure Key Vault
  • container_registry.bicep — Azure Container Registry
  • monitoring.bicep — Log Analytics & App Insights
  • container_apps.bicep — Container Apps Environment, Apps & managed cert
  • dns_zone.bicep — Azure DNS Zone + TXT/CNAME records
  • app_service_domain.bicep — Domain purchase via Azure App Service Domain
  • parameters.dev.bicepparam — Development parameters (deploy_custom_domain: true, deploy_swa_custom_domain: true)
  • parameters.prod.bicepparam — Production parameters (deploy_custom_domain: true, deploy_swa_custom_domain: true)
  • parameters.shared.bicepparam — Shared parameters (purchase_domain: true)
  • scripts/setup-oidc.sh — OIDC / GitHub Actions authentication setup
  • scripts/teardown-oidc.sh — OIDC teardown / cleanup

Parameters

main.bicep (per-environment)

Parameter Description Default
environment Environment name (dev/prod) dev
location Azure region westeurope
admin_email Admin email for notifications -
custom_domain Custom domain hostname to bind ''
deploy_containers Deploy Container Apps images false
deploy_custom_domain Issue managed cert and bind SniEnabled false
swa_custom_domain Custom domain for Static Web App ''
deploy_swa_custom_domain Bind SWA custom domain (cname-delegation) false

shared.bicep (once, environment-agnostic)

Parameter Description Default
domain_name Root domain (e.g. sven-relijveld.com) -
purchase_domain Purchase domain via App Service Domain false
contact_* Registrant info (non-sensitive, in file) -
contact_phone/address/city/* Injected from GitHub Secrets at runtime -
ca_env_verification_id Prod CA env verification ID (discovered at runtime) ''
frontend_fqdn Prod frontend Container App FQDN (discovered) ''
dev_ca_env_verification_id Dev CA env verification ID (discovered at runtime) ''
dev_frontend_fqdn Dev frontend Container App FQDN (discovered) ''
prod_swa_hostname Prod SWA default hostname (discovered at runtime) ''
dev_swa_hostname Dev SWA default hostname (discovered at runtime) ''

Outputs

After deployment, you'll receive:

  • resource_group_name - Resource Group name
  • key_vault_name - Key Vault name
  • container_registry_name - Container Registry name
  • container_registry_login_server - ACR login server
  • frontend_url - Frontend URL
  • backend_url - Backend URL
  • app_insights_instrumentation_key - Application Insights key

Cost Estimate

Development Environment:

  • Container Apps: ~$5-10/month (0.25 vCPU, 0.5 GB RAM)
  • Container Registry (Basic): ~$5/month
  • Log Analytics: ~$2-5/month (30-day retention)
  • Key Vault: ~$0.03/month
  • Total: ~$12-20/month

Production Environment:

  • Similar costs with potential scaling
  • Custom domain: Free (HTTPS included)
  • Total: ~$15-25/month

Security

  • Managed identities for authentication
  • Key Vault for secrets
  • RBAC for access control
  • HTTPS enforced
  • Network isolation available

Monitoring

  • Application Insights for telemetry
  • Log Analytics for centralized logging
  • Health probes for availability
  • Auto-scaling based on load

Code Standards

All Bicep code follows:

  • snake_case for variables, parameters, and resource names
  • CAF naming conventions for Azure resources
  • Modular architecture for reusability
  • Comprehensive documentation

Troubleshooting

Deployment fails

# Check deployment status
az deployment sub show --name <deployment-name>

# View deployment logs
az deployment sub show --name <deployment-name> --query properties.error

Container Apps not starting

# View container logs
az containerapp logs show --name dna-dev-portfolio-we-ca-fe --resource-group dna-dev-portfolio-westeurope-rg

# Check revision status
az containerapp revision list --name dna-dev-portfolio-we-ca-fe --resource-group dna-dev-portfolio-westeurope-rg

Key Vault access issues

# Grant yourself access
az keyvault set-policy --name <vault-name> --upn <your-email> --secret-permissions get list set

Cleanup

To delete all resources:

# Delete resource group
az group delete --name dna-dev-portfolio-westeurope-rg --yes --no-wait

Next Steps

Support

For issues or questions: