Terraform Users Guide for ADC
Table of Contents
- Introduction
- Repository Overview
- Repository Structure
- Naming Conventions
- Networking Layer
- Shared Services Layer
- Application Layer
- Identity and Permissions
- Application Registrations
- Code Examples
- Best Practices
- Troubleshooting
Introduction
This repository contains Terraform Infrastructure as Code (IaC) for deploying Azure Landing Zone components for the ADC (Application Development Center) environment. It leverages Spacelift modules from the private registry us.spacelift.io/claconnect.
Owner Information
- Owner: Aaron Alex
- Ops Team: Data Operations
- Support Contact: Jon Cormack
Purpose
This repository provisions and manages Azure resources across multiple environments (dev, qa, uat, prd) and regions (eastus2, centralus) using a three-layer architecture pattern.
Repository Overview
Architecture Layers
The repository is organized into three distinct layers:
-
Networking Layer (
networking/)- Virtual networks and subnets
- Network security configuration
- Network watchers
-
Shared Services Layer (
shared_services/)- Container Registry (ACR)
- Shared Key Vaults
- Shared Storage Accounts
- Subscription budgets
-
Application Layer (
app/)- AKS Clusters
- SQL Servers and Databases
- Redis Cache
- Key Vaults (app-specific)
- Storage Accounts
- Service Bus
- App Configuration
- User Assigned Managed Identities
- Application Registrations
Module Source Pattern
All modules are sourced from the Spacelift private registry:
Repository Structure
Key Files in Application Layer
Naming Conventions
The repository uses a consistent naming convention across all resources:
Pattern
Examples
- Resource Group:
dev-eus2-adc-app-rg-001 - AKS Cluster:
dev-eus2-adc-aks-001 - Key Vault:
dev-eus2-adc-app-kv-001 - SQL Server:
dev-eus2-adc-sql-001 - Storage Account:
deveus2adcinst001(lowercase, no hyphens)
Abbreviations
Naming Logic in locals.tf
All resource names are computed in locals.tf:
Networking Layer
Purpose
Provisions foundational networking infrastructure including VNets, subnets, and network security components.
Components
- Virtual Networks: Address space allocation per environment
- Subnets: Segmentation for different workloads
- Private Endpoints subnet
- AKS Cluster subnet
- AKS Load Balancer subnet
- Function App subnet
- Network Watcher: Network monitoring and diagnostics
Configuration Example
From networking/config/dev.eastus2.auto.tfvars:
Resource Modules Used
claconnect/resource_group/azurermclaconnect/network_watcher/azurermclaconnect/virtual_network/azurermclaconnect/subnets/azurerm
Shared Services Layer
Purpose
Deploys resources shared across multiple applications within the environment.
Components
- Container Registry (ACR): Docker image storage
- Key Vaults: Shared secrets management
- Storage Accounts: Shared storage
- Subscription Budgets: Cost management
Configuration Structure
From shared_services/config/dev.eastus2.auto.tfvars:
Key Vault Configuration Example
Key Vaults use random ID suffixes for global uniqueness:
Application Layer
Overview
The application layer contains the bulk of the infrastructure, including compute, storage, databases, and identity resources.
AKS Cluster
Configuration Files
aks_cluster.tf: Cluster and node pool definitions
Module Configuration
Maintenance Windows
Configure maintenance windows in tfvars:
SQL Server and Databases
Configuration Files
database.tf: SQL server and database definitions
SQL Server Module
SQL Database Module
Auditing Configuration
Redis Cache
Configuration Files
redis.tf: Redis cache instances
Redis Module
Key Vault
Configuration Files
kv.tf: Primary Key Vaultkv_02.tf: Secondary Key Vault
Key Vault Module
Secrets Management
Secrets are defined in locals.tf and created via module:
Storage Accounts
Configuration Files
storage.tf: Storage account and container definitions
Storage Account Module
Variable Configuration Example
Service Bus
Configuration Files
servicebus.tf: Namespace, topics, and subscriptions
Service Bus Namespace
Service Bus Topics
Service Bus Subscriptions
Variable Configuration Example
App Configuration
Configuration Files
app_config.tf: App Configuration service
App Configuration Module
Identity and Permissions
User Assigned Managed Identities (UAMIs)
Configuration Files
managed_identity.tf: UAMI definitionsroles.tf: Role assignments
UAMI Module
Federated Credentials
The module supports two types of federated credentials:
- GitHub Actions - For CI/CD pipelines
- Workload Identity - For AKS workloads
Example variable configuration:
Role Assignments
Permission Flags
User Assigned Managed Identities can be assigned permissions using boolean flags in the variable definition. The following flags are available:
Role Assignment Pattern
All role assignments follow a consistent pattern using for_each and lookup:
Pre-deployed Identity Role Assignments
Some identities are pre-deployed and referenced via data sources:
Application Registrations
Configuration Files
app_reg.tf: App registration definitions
Components
- UUID Generation: Unique IDs for API URIs, permissions, and app roles
- Application Registration: Azure AD app creation
- App Roles: Custom roles for users and applications
- OAuth2 Permission Scopes: Delegated permissions
- Role Assignments: Mapping AAD groups and service principals to app roles
Application Registration Module
Variable Configuration Example
App Role Assignments
App roles are assigned to Azure AD groups and service principals:
Code Examples
Example 1: Adding a New AKS Cluster
Not applicable - The repository uses a singleton pattern for AKS. To modify the existing cluster, update variables in the tfvars file.
To change node pool configuration:
Example 2: Adding a New Redis Cache
Step 1: Add local name in locals.tf
Step 2: Add variables in variables.tf
Step 3: Add module in redis.tf
Step 4: Set values in tfvars
Example 3: Adding a New SQL Database
Step 1: Add local name in locals.tf
Step 2: Add variables in variables.tf
Step 3: Add module in database.tf
Step 4: Set values in tfvars
Example 4: Adding a New UAMI with Permissions
Step 1: Add UAMI configuration in tfvars
Step 2: Apply Terraform
The UAMI will be created automatically with:
- Name:
dev-eus2-adc-new-app-uami-001 - Federated credentials for GitHub Actions and AKS Workload Identity
- All specified role assignments
No changes to .tf files required - the existing module and role assignment patterns handle everything!
Example 5: Adding Storage Account with Role Assignments
Step 1: Add storage account configuration in tfvars
Step 2: Grant permissions to UAMI
If you need a UAMI with specific permissions to this storage account, you would need to:
- Add new permission flags to role assignment modules in
roles.tf:
- Update UAMI configuration in tfvars:
Example 6: Adding Service Bus Topics and Subscriptions
Step 1: Add topic configuration in tfvars
Step 2: Add subscription configuration
No changes to .tf files required - existing modules handle the creation!
Best Practices
1. Naming Consistency
- Always use the naming pattern defined in
locals.tf - Never hardcode resource names in modules
- Use environment and location abbreviations consistently
2. Module Versioning
- Pin module versions using
version = "x.y.z" - Avoid using
version = "~> x.y"in production - Review module changes before upgrading versions
3. Security
- Never disable RBAC authorization on Key Vaults
- Use Azure AD authentication only for SQL Servers (no SQL auth)
- Enable Entra ID authentication for Redis (disable access keys)
- Use private endpoints for all PaaS services
- Implement network security with IP allowlists and subnet restrictions
4. Tags
- Apply consistent tags across all resources
- Include at minimum:
OwnerEnvironmentCreatedByLocationCostCenterCriticalityApplicationNameTFManagedOpsTeam
5. Identity Management
- Use User Assigned Managed Identities over service principals
- Implement Federated Credentials for GitHub Actions (no secrets!)
- Use Workload Identity for AKS pods
- Apply least privilege principle with role assignments
6. Configuration Management
- Keep environment-specific configuration in
.auto.tfvarsfiles - Use variables for all configurable values
- Document variable defaults in
variables.tf
7. State Management
- Use remote state backend (likely Spacelift)
- Never commit
.tfstatefiles - Use state locking
8. Private Endpoints
- Always create private endpoints for:
- Key Vault
- Storage Accounts
- SQL Server
- Redis Cache
- Service Bus
- App Configuration
- AKS (private cluster when required)
9. DNS Configuration
- All private DNS zones are centrally managed in
prd-eus2-identity-dns-rg-001 - Reference DNS zone IDs from locals, don’t hardcode
10. Maintenance Windows
- Configure maintenance windows for AKS clusters
- Schedule during low-traffic periods
- Use CST timezone offset (
-06:00)
Troubleshooting
Common Issues
Issue 1: Key Vault Access Denied During Deployment
Symptom: Error: authorization failed when creating Key Vault secrets
Solution: Ensure the deployment identity has Key Vault Secrets Officer role:
Add depends_on to secret creation:
Issue 2: Private Endpoint DNS Resolution Failing
Symptom: Cannot connect to resource via private endpoint
Solution:
- Verify private DNS zone exists in central DNS resource group
- Confirm DNS zone ID in
locals.tfis correct - Check VNet is linked to private DNS zone
- Verify private endpoint is in the correct subnet
Issue 3: Storage Account Name Conflict
Symptom: Error: storage account name already exists
Solution: Storage account names must be globally unique and lowercase. Check:
- Naming logic in
locals.tfgenerates unique names - No special characters (only alphanumeric)
- Maximum 24 characters
- Storage account key in variable matches naming logic
Issue 4: AKS Cluster Upgrade Failure
Symptom: AKS upgrade fails during maintenance window
Solution:
- Check node pool SKU availability in target region
- Verify Kubernetes version is supported
- Review maintenance window configuration
- Check for pod disruption budgets blocking drain
Issue 5: Service Bus Permission Denied
Symptom: Application cannot send/receive messages
Solution: Verify UAMI has correct role assignment flags:
Issue 6: Module Version Not Found
Symptom: Error: module version not found in registry
Solution:
- Verify you have access to Spacelift registry
- Check module version exists
- Ensure provider authentication is configured
- Try
terraform init -upgrade
Issue 7: For_Each Errors
Symptom: Error: Invalid for_each argument
Solution:
- Ensure the variable is a map, not a list
- Verify all map keys are strings
- Check for null values in map
- Validate variable structure matches expected schema
Deployment Workflow
Initial Setup
- Clone Repository
- Select Layer and Environment
- Initialize Terraform
- Select Workspace (if using workspaces)
Making Changes
-
Update Configuration
- Modify
.auto.tfvarsfile for environment-specific changes - Update
.tffiles for structural changes
- Modify
-
Plan Changes
-
Review Plan
- Verify expected resources will be created/modified/destroyed
- Check for unintended changes
-
Apply Changes
Layer Deployment Order
When deploying a new environment, follow this order:
- Networking Layer - Foundation network infrastructure
- Shared Services Layer - Cross-application resources
- Application Layer - Application-specific resources
Spacelift Integration
If using Spacelift for automation:
- Stacks are likely configured per layer per environment
- Configuration files (
.auto.tfvars) trigger stack runs - Review plans in Spacelift UI before confirming
- Use stack dependencies to enforce deployment order
Additional Resources
Private DNS Zones (Centrally Managed)
All private DNS zones are managed in subscription b83668dc-1957-4664-b687-7bd4ae39d41a:
Pre-deployed Resources
Some resources are pre-deployed and referenced via data sources:
- AKS UAMI:
{env}-{loc}-{app}-aks-uami-001 - Application Gateway UAMI:
{env}-{loc}-apim-hub-webagw-uami-001 - App Insights:
{env}-{loc}-{app}-appinsights-001 - Operations Log Analytics:
prdeus2opslog001(production subscription)
Contact Information
For questions or issues:
- Owner: Aaron Alex
- Ops Team: Data Operations
- Support: Jon Cormack
Appendix: Variable Reference
Common Variables
Security Variables
GitHub Copilot Instructions
Overview
To enhance your development experience with GitHub Copilot in this repository, you can create a .github/copilot-instructions.md file that provides context-specific guidance to Copilot about this infrastructure codebase.
Creating the Instructions File
Step 1: Create the Directory Structure
Step 2: Create the Instructions File
Create .github/copilot-instructions.md with the following content:
{environment}-{location}-{application}-{resource_type}-{suffix}
Always Pin Versions
- Use exact versions:
version = "1.0.5" - Never use:
version = "~> 1.0"in production code
Security Requirements
Authentication & Authorization
- SQL Servers: Always use Azure AD authentication only (
administrator_login = null,azuread_administrator_azread_authentication_only = true) - Key Vaults: Always enable RBAC authorization (
rbac_authorization_enabled = true) - Redis: Always use Entra ID authentication (
access_keys_authentication_enabled = false) - Service Bus: Disable local auth (
local_auth_enabled = false)
Network Security
- Always create private endpoints for PaaS services
- Use
network_default_action = "Deny"with explicit IP allowlists - Reference centralized private DNS zones from
locals.tf
Identity Management
- Prefer User Assigned Managed Identities over service principals
- Use Federated Credentials for GitHub Actions (no secrets!)
- Implement Workload Identity for AKS pods
- Apply least privilege with permission flags
Code Patterns
User Assigned Managed Identities (UAMIs)
When suggesting UAMI additions, use permission flags in tfvars:
Role Assignments Pattern
Role assignments use for_each with lookup pattern:
Private Endpoint Pattern
Always create private endpoints with DNS integration:
Storage Account Pattern
Storage accounts use for_each over a variable map:
Configuration Best Practices
Variable Configuration Location
- Environment-specific values go in
config/*.auto.tfvars - Variable declarations go in
variables.tf - Computed values go in
locals.tf - Never hardcode values in
.tffiles
Tags
Always include these tags:
Service Bus Pattern
Service Bus uses three-tier configuration:
- Namespace - One per environment
- Topics - Multiple per namespace
- Subscriptions - Multiple per topic
File Organization
Application Layer Files
main.tf- Resource group onlyprovider.tf- Provider configurationvariables.tf- All variable declarations (1100+ lines)locals.tf- Naming logic and computed valuesdata.tf- Data source queriesaks_cluster.tf- AKS resourcesdatabase.tf- SQL resourcesredis.tf- Redis cachekv.tf,kv_02.tf- Key Vaultsstorage.tf- Storage accountsservicebus.tf- Service Busmanaged_identity.tf- UAMIsapp_reg.tf- App registrationsroles.tf- Role assignmentsapp_config.tf- App Configurationoutput.tf- Outputs
Common Patterns to Suggest
Adding a New Resource Type
- Add naming logic to
locals.tf - Add variable declarations to
variables.tfwith defaults - Create module in appropriate
.tffile - Create private endpoint (if applicable)
- Add values to
config/*.auto.tfvars
Adding a New UAMI
Only modify tfvars - no .tf changes needed:
Adding Storage Permissions to Existing UAMI
- Add role assignment module in
roles.tf - Add permission flag to UAMI in tfvars
What NOT to Suggest
- ❌ Hardcoded resource names in modules
- ❌ SQL authentication with username/password
- ❌ Access keys for Redis
- ❌ Service Bus with local auth enabled
- ❌ Resources without tags
- ❌ PaaS services without private endpoints
- ❌ Unpinned module versions
- ❌ Creating new AKS clusters (singleton pattern)
DNS Zones Reference
All private DNS zones are centrally managed in subscription b83668dc-1957-4664-b687-7bd4ae39d41a:
- AKS:
privatelink.eastus2.azmk8s.io - Storage Blob:
privatelink.blob.core.windows.net - SQL:
privatelink.database.windows.net - Key Vault:
privatelink.vaultcore.azure.net - Service Bus:
privatelink.servicebus.windows.net - Redis:
privatelink.redis.cache.windows.net - App Configuration:
privatelink.azconfig.io
Deployment Context
- Using Spacelift for CI/CD
- Deploy order: Networking → Shared Services → Application
- Each layer/environment likely has its own Spacelift stack
- Changes to
.auto.tfvarstrigger deployments
When Suggesting Code
- Always follow existing patterns in the repository
- Use the same module versions as similar resources
- Include all required security configurations
- Add both the resource and its private endpoint
- Reference locals for names, not hardcode
- Include depends_on when necessary (e.g., role assignments)
- Suggest tfvars changes when possible instead of .tf changes
- Include tags in all resources
Contact Information
- Owner: Aaron Alex
- Ops Team: Data Operations
- Support: Jon Cormack
How It Works
Once this file is in place:
- Automatic Context: GitHub Copilot will automatically read and use these instructions when providing suggestions in this repository
- Consistent Patterns: Copilot will follow the naming conventions, security requirements, and code patterns defined
- Better Suggestions: Copilot will understand the three-layer architecture and suggest appropriate module usage
- Security Compliance: Copilot will suggest secure configurations by default (RBAC, private endpoints, etc.)
Benefits
- Faster Development: Copilot understands your repository structure and patterns
- Consistency: All suggestions follow established conventions
- Security: Security-first suggestions aligned with best practices
- Reduced Errors: Copilot avoids anti-patterns specific to this codebase
- Team Alignment: New team members get consistent AI assistance
Testing the Instructions
After adding the file, test it by:
- Opening a
.tffile in VS Code - Starting to type a new resource (e.g., “module “new_redis”)
- Observing that Copilot suggestions follow the patterns defined in the instructions
- Asking Copilot Chat questions like “How do I add a new UAMI with storage permissions?”
Updating Instructions
As the repository evolves, update .github/copilot-instructions.md to:
- Add new patterns and conventions
- Document new resource types
- Update security requirements
- Add lessons learned from common mistakes
Document Version: 1.0
Last Updated: January 22, 2026
Terraform Version: >= 1.0
Module Registry: us.spacelift.io/claconnect