Multi-Cloud Terraform Configuration¶
Overview¶
Omnistrate allows you to define separate Terraform stacks for each cloud provider — AWS, GCP, Azure, OCI, and Nebius — 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, azure, oci, and nebius. 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
oci:
terraformPath: /terraform/oci
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
nebius:
terraformPath: /terraform/nebius
serviceAccountID: serviceaccount-e00vqdp9fskhmmaan8
publicKeyID: publickey-e00h9scsyy9mbefrjf
privateKeyPEM: $secret.nebiusTerraformPrivateKey
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
│ ├── oci/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── nebius/
│ │ ├── 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, Azure, OCI, and Nebius 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
}
Nebius Provider Block¶
Nebius Terraform stacks should declare the Nebius provider, but leave credential injection to Omnistrate:
terraform {
required_providers {
nebius = {
source = "terraform-provider.storage.eu-north1.nebius.cloud/nebius/nebius"
}
}
}
provider "nebius" {
domain = "api.eu.nebius.cloud:443"
}
Configure the credentials for that stack on the Plan entry by setting serviceAccountID, publicKeyID, and privateKeyPEM under configurationPerCloudProvider.nebius.
OCI Stack¶
provider "oci" {
region = "{{ $sys.deploymentCell.region }}"
}
resource "oci_database_autonomous_database" "database" {
compartment_id = "{{ $sys.deploymentCell.oci.clusterCompartmentOCID }}"
display_name = "db-{{ $sys.id }}"
db_name = "db{{ $sys.id }}"
admin_password = var.db_password
cpu_core_count = 1
data_storage_size_in_tbs = 1
db_version = "19c"
db_workload = "OLTP"
is_free_tier = false
}
output "database_endpoint" {
value = oci_database_autonomous_database.database.connection_urls[0].sql_dev_web_url
}
output "database_port" {
value = 1522
}
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¶
For non-Nebius providers, you can configure a custom execution identity such as an IAM role (AWS) or service account (GCP) 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
Nebius is different. Use explicit service-account auth on the nebius entry instead of terraformExecutionIdentity:
terraformConfigurations:
configurationPerCloudProvider:
nebius:
terraformPath: /terraform/nebius
serviceAccountID: "serviceaccount-e00vqdp9fskhmmaan8"
publicKeyID: "publickey-e00h9scsyy9mbefrjf"
privateKeyPEM: "$secret.nebiusTerraformPrivateKey"
gitConfiguration:
reference: refs/heads/main
repositoryUrl: https://github.com/your-org/infra-repo.git
Notes:
- All three Nebius auth fields are required on
configurationPerCloudProvider.nebius. - Prefer
$secret.<name>forprivateKeyPEM. terraformExecutionIdentityis not supported for Nebius.
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
nebius:
terraformPath: /terraform/nebius
serviceAccountID: "serviceaccount-e00vqdp9fskhmmaan8"
publicKeyID: "publickey-e00h9scsyy9mbefrjf"
privateKeyPEM: "$secret.nebiusTerraformPrivateKey"
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, Azure, OCI, or Nebius, the matching stack runs instead. 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