Multi-tenant management patterns for Microsoft 365 using Terraform across multiple tenants and organizations
This guide covers strategies and best practices for managing Microsoft 365 resources across multiple tenants using Terraform. Whether you’re managing environments for multiple customers, business units, or subsidiaries, these patterns will help you maintain consistency while respecting tenant boundaries.
Each tenant requires separate authentication credentials
Resources cannot be shared across tenant boundaries
Configuration drift across tenants is common
Compliance requirements may vary by tenant
Deployment schedules differ across organizations
This guide focuses on managing multiple Microsoft 365 tenants, not to be confused with multi-environment management within a single tenant (dev/staging/prod).
Pattern 2: Provider aliasing for multi-tenant deployment
Use Terraform provider aliases to manage multiple tenants from a single workspace.
provider "microsoft365" { alias = "tenant_a" cloud = "public" tenant_id = var.tenant_a_id auth_method = "client_secret" entra_id_options = { client_id = var.tenant_a_client_id client_secret = var.tenant_a_client_secret }}provider "microsoft365" { alias = "tenant_b" cloud = "public" tenant_id = var.tenant_b_id auth_method = "client_secret" entra_id_options = { client_id = var.tenant_b_client_id client_secret = var.tenant_b_client_secret }}# Deploy to tenant Aresource "microsoft365_graph_beta_conditional_access_policy" "mfa_tenant_a" { provider = microsoft365.tenant_a display_name = "Require MFA for All Users" state = "enabled" # ... configuration}# Deploy identical policy to tenant Bresource "microsoft365_graph_beta_conditional_access_policy" "mfa_tenant_b" { provider = microsoft365.tenant_b display_name = "Require MFA for All Users" state = "enabled" # ... configuration}
Advantages:
Single workspace manages multiple tenants
Easy to deploy identical resources across tenants
Simplified CI/CD pipeline
Disadvantages:
All credentials in one workspace (security concern)
State file contains all tenants (blast radius)
Difficult to provide tenant-specific access control
Best For:
Small number of closely related tenants
Deploying identical configurations
Internal multi-tenant scenarios (not MSPs)
Security consideration: This pattern stores credentials for all tenants in a single workspace. Use only when all operators should have access to all tenants.
v1.0.0 → v1.1.0 - Safe, backward-compatible features
v1.0.0 → v2.0.0 - Breaking changes, test thoroughly
Implement drift detection
Schedule regular drift detection across all tenants:
# Run daily via cron/GitHub Actionsfor tenant in tenant-a tenant-b tenant-c; do terraform workspace select $tenant terraform plan -detailed-exitcode -parallelism=1done
Test changes in pilot tenant first
Designate one tenant as your canary:
# Deploy order: pilot → staging tenants → production tenants1. tenant-pilot (your test tenant)2. tenant-staging-a, tenant-staging-b (subset of prod)3. tenant-prod-1, tenant-prod-2, ..., tenant-prod-n (all production)
Symptom: Deployment works for some tenants but not othersCommon causes:
Service principal not created in target tenant
Insufficient permissions granted in tenant
Conditional access blocking service principal
Solution:
# Verify service principal exists in tenantaz ad sp list --filter "appId eq '<client-id>'" --query "[].{DisplayName:displayName, AppId:appId}" -o table# Check permissions grantedaz ad sp show --id <client-id> --query "appRoles[].{Role:value, Granted:allowedMemberTypes}" -o table
Resource conflicts across tenants
Symptom: Resource names/IDs conflicting across tenants in shared stateSolution: Use tenant-specific resource naming: