Skip to content

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 provider
  • gitConfiguration: 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