Skip to content

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 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
│   ├── 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> for privateKeyPEM.
  • terraformExecutionIdentity is 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