Prompt
How do I set up infrastructure as code for AWS and Azure in one repo?
Latest observation
A good way to do AWS + Azure infrastructure-as-code in one repo is to use a monorepo with clear boundaries, and choose either:
- One IaC tool that supports both clouds, or
- Separate IaC stacks per cloud managed in one repo
Recommended approaches
Option A: Terraform in one repo
This is the most common choice.
Why
- Supports both AWS and Azure
- Strong module ecosystem
- Easy to organize by environment and cloud
- Works well in CI/CD
Typical repo layout
infra/
modules/
networking/
compute/
iam/
aws/
dev/
main.tf
variables.tf
outputs.tf
backend.tf
prod/
main.tf
variables.tf
outputs.tf
backend.tf
azure/
dev/
main.tf
variables.tf
outputs.tf
backend.tf
prod/
main.tf
variables.tf
outputs.tf
backend.tf
shared/
tags/
naming/
live/
dev/
aws/
azure/
prod/
aws/
azure/
A simpler structure is often better:
infra/
modules/
environments/
dev/
aws/
azure/
prod/
aws/
azure/
Option B: Use separate tools but one repo
Example:
- Terraform for AWS
- Bicep for Azure
This can be useful if your Azure team prefers native Azure templates, but it adds operational complexity. You’ll need:
- separate linting and validation
- different deployment commands
- separate state/outputs handling
Best practice: keep cloud stacks separate
Even in one repo, avoid mixing AWS and Azure resources in the same root module unless there’s a strong reason.
Instead:
- one root stack for AWS dev
- one root stack for Azure dev
- one root stack for AWS prod
- one root stack for Azure prod
This keeps:
- state files isolated
- permissions cleaner
- deployments safer
- blast radius smaller
State management
Use a separate backend/state per environment and cloud.
For Terraform:
- AWS backend: S3 + DynamoDB locking
- Azure backend: Azure Storage account blob backend
Example:
aws-dev.tfstateaws-prod.tfstateazure-dev.tfstateazure-prod.tfstate
Do not share one state file across clouds.
Authentication
Set up each cloud with its own identity method.
AWS
- Prefer OIDC from CI/CD to assume an IAM role
- Avoid long-lived access keys
Azure
- Use an Azure AD app / service principal, or better, federated identity via OIDC if supported by your CI/CD
CI/CD structure
Your pipeline should:
- detect which folder changed
- run format/validate
- plan only the affected stack
- require approval for prod
- apply only after approval
Example pipeline logic:
- if
infra/aws/dev/**changes → plan/apply AWS dev - if
infra/azure/prod/**changes → plan/apply Azure prod
Module design tips
Make modules cloud-specific unless they are truly reusable.
Good examples:
aws-vpcazure-vnetaws-eksazure-aks
Shared logic can be abstracted into:
- naming conventions
- tagging standards
- common variables
- policy checks
Recommended repo pattern
If you want a clean and scalable setup, use this:
infra/
modules/
aws/
azure/
common/
live/
dev/
aws/
azure/
staging/
aws/
azure/
prod/
aws/
azure/
Each live/.../... folder is a deployable stack with its own backend.
Tooling extras worth adding
- Terraform fmt
- tflint
- checkov or tfsec
- pre-commit
- GitHub Actions or Azure DevOps
- policy-as-code with OPA/Conftest or Sentinel if needed
When to use separate repos instead
Use separate repos if:
- different teams manage AWS and Azure
- different release cadences
- very large codebases
- strict access separation is required
Practical recommendation
If you want the simplest, most maintainable answer:
- use Terraform
- keep AWS and Azure in the same repo
- separate them by environment and cloud
- use one state per stack
- use OIDC auth in CI/CD
- use shared modules only for truly common logic
If you want, I can also give you:
- a sample Terraform monorepo folder structure,
- a GitHub Actions pipeline for AWS + Azure, or
- a Terraform backend setup for both clouds.