Multi-Cloud Terraform Configuration¶
Overview¶
Omnistrate allows you to define separate Terraform stacks for each cloud provider — AWS, GCP, and Azure — within a single Plan specification. At deployment time, the platform automatically selects and executes the correct stack based on the target cloud provider, letting you offer a multi-cloud SaaS Product from one specification.
This approach avoids complex conditionals inside a single Terraform configuration. Instead, you maintain clean, provider-specific stacks that follow each cloud's best practices.
Configuring Per-Cloud-Provider Stacks¶
The configurationPerCloudProvider property under terraformConfigurations accepts entries for aws, gcp, and azure. Each entry points to a Terraform stack in your Git repository.
Basic Structure¶
services:
- name: cloudInfra
internal: true
terraformConfigurations:
configurationPerCloudProvider:
aws:
terraformPath: /terraform/aws
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
gcp:
terraformPath: /terraform/gcp
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
azure:
terraformPath: /terraform/azure
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
Each cloud provider entry requires:
terraformPath: The directory path within the repository containing the Terraform stack for that providergitConfiguration: The Git repository URL and branch/tag reference
Repository Layout¶
A common repository structure for multi-cloud Terraform stacks:
infra-repo/
├── terraform/
│ ├── aws/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── gcp/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── azure/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
Tip
Keep consistent output names across cloud providers. If your AWS stack outputs database_endpoint, your GCP and Azure stacks should output the same key. This ensures dependent resources can reference outputs without cloud-specific logic.
Cloud-Specific Examples¶
AWS Stack¶
provider "aws" {
region = "{{ $sys.deploymentCell.region }}"
}
resource "aws_db_instance" "database" {
identifier = "db-{{ $sys.id }}"
engine = "postgres"
instance_class = "db.t3.medium"
allocated_storage = 20
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
username = "admin"
password = var.db_password
skip_final_snapshot = true
}
resource "aws_security_group" "db" {
name = "db-sg-{{ $sys.id }}"
vpc_id = "{{ $sys.deploymentCell.cloudProviderNetworkID }}"
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["{{ $sys.deploymentCell.cidrRange }}"]
}
}
resource "aws_db_subnet_group" "main" {
name = "db-subnet-{{ $sys.id }}"
subnet_ids = [
"{{ $sys.deploymentCell.privateSubnetIDs[0].id }}",
"{{ $sys.deploymentCell.privateSubnetIDs[1].id }}"
]
}
output "database_endpoint" {
value = aws_db_instance.database.endpoint
}
output "database_port" {
value = aws_db_instance.database.port
}
GCP Stack¶
provider "google" {
region = "{{ $sys.deploymentCell.region }}"
}
resource "google_sql_database_instance" "database" {
name = "db-{{ $sys.id }}"
database_version = "POSTGRES_15"
region = "{{ $sys.deploymentCell.region }}"
settings {
tier = "db-f1-micro"
ip_configuration {
ipv4_enabled = false
private_network = "{{ $sys.deploymentCell.cloudProviderNetworkID }}"
}
}
deletion_protection = false
}
resource "google_sql_user" "admin" {
name = "admin"
instance = google_sql_database_instance.database.name
password = var.db_password
}
output "database_endpoint" {
value = google_sql_database_instance.database.private_ip_address
}
output "database_port" {
value = 5432
}
Azure Stack¶
provider "azurerm" {
features {}
}
resource "azurerm_postgresql_flexible_server" "database" {
name = "db-{{ $sys.id }}"
resource_group_name = var.resource_group_name
location = "{{ $sys.deploymentCell.region }}"
version = "15"
administrator_login = "admin"
administrator_password = var.db_password
sku_name = "B_Standard_B1ms"
storage_mb = 32768
zone = "1"
}
output "database_endpoint" {
value = azurerm_postgresql_flexible_server.database.fqdn
}
output "database_port" {
value = 5432
}
Using System Parameters Across Clouds¶
Omnistrate provides cloud-agnostic system parameters that work across all providers. Use these to inject deployment context into your Terraform templates:
| Parameter | Description |
|---|---|
$sys.deploymentCell.region | The deployment region (e.g., us-east-1, us-central1, eastus) |
$sys.deploymentCell.cloudProviderNetworkID | The VPC/VNet/Network ID for the deployment cell |
$sys.deploymentCell.cidrRange | The CIDR range assigned to the deployment cell |
$sys.deploymentCell.publicSubnetIDs[i].id | Public subnet IDs available in the deployment cell |
$sys.deploymentCell.privateSubnetIDs[i].id | Private subnet IDs available in the deployment cell |
$sys.id | Unique deployment identifier, useful for naming resources |
For the full list, see System Parameters.
Warning
Ensure the main .tf file for each cloud provider stack includes the provider definition. Omnistrate requires the provider block to be present in the top-level Terraform file.
Versioning with Git Tags¶
Use Git tags to version your Terraform stacks and ensure consistency across Plan releases:
terraformConfigurations:
configurationPerCloudProvider:
aws:
terraformPath: /terraform/aws
gitConfiguration:
reference: refs/tags/v1.2.0
repositoryUrl: https://github.com/your-org/infra-repo.git
Each Plan version can reference a specific Git tag, making deployments reproducible, and upgrades controlled.
Private Repository Access¶
For private Git repositories, provide an access token:
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/private-infra-repo.git
accessToken: <GITHUB_PAT>
Custom Execution Identity¶
You can configure a custom IAM role (AWS) or service account (GCP) for Terraform execution using the terraformExecutionIdentity property:
terraformConfigurations:
configurationPerCloudProvider:
aws:
terraformPath: /terraform/aws
terraformExecutionIdentity: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-custom-terraform-role"
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
gcp:
terraformPath: /terraform/gcp
terraformExecutionIdentity: "omnistrate-tf-sa@<GCP_PROJECT_ID>.iam.gserviceaccount.com"
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
For more details on custom execution identities, pre-created principals, and BYOC Terraform permissions, see the Getting Started with Terraform guide.
Full Multi-Cloud Example¶
The following Plan specification defines a Terraform resource that provisions a database on each cloud, with a Helm chart application consuming the database endpoint:
name: Multi-Cloud SaaS Product
deployment:
hostedDeployment:
awsAccountId: "<AWS_ACCOUNT_ID>"
awsBootstrapRoleAccountArn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-bootstrap-role
gcpProjectId: "<GCP_PROJECT_ID>"
gcpProjectNumber: "<GCP_PROJECT_NUMBER>"
gcpServiceAccountEmail: "<GCP_SA_EMAIL>"
services:
- name: dbInfra
internal: true
terraformConfigurations:
configurationPerCloudProvider:
aws:
terraformPath: /terraform/aws
gitConfiguration:
reference: refs/tags/v1.0.0
repositoryUrl: https://github.com/your-org/infra-repo.git
gcp:
terraformPath: /terraform/gcp
gitConfiguration:
reference: refs/tags/v1.0.0
repositoryUrl: https://github.com/your-org/infra-repo.git
- name: WebApp
dependsOn:
- dbInfra
network:
ports:
- 8080
helmChartConfiguration:
chartName: web-app
chartVersion: 2.0.0
chartRepoName: my-charts
chartRepoURL: https://charts.example.com
chartValues:
database:
host: "{{ $dbInfra.out.database_endpoint }}"
port: "{{ $dbInfra.out.database_port }}"
When a customer deploys this Plan on AWS, the AWS Terraform stack runs. When deployed on GCP, the GCP stack runs. The dependent Helm chart receives the correct database endpoint regardless of cloud provider.
Next Steps¶
- Input Parameters and Output Mapping: Pass dynamic input variables and map Terraform outputs to other resources
- Helm and Terraform: Detailed walkthrough of combining Helm charts with Terraform
- Custom Terraform Permissions: Configure IAM policies for BYOC deployments