Skip to content

Building your SaaS Product using Terraform

Getting started

The Omnistrate Terraform Integration streamlines the deployment and management of cloud infrastructure by combining the power of Terraform with the flexibility of Omnistrate. This integration allows you to inject dynamic metadata directly into your Terraform templates, automate complex multi-cloud deployments, and modularize infrastructure components for efficient management. By integrating with other deployment types such as Helm charts, Operators or Kustomize, Omnistrate enhances orchestration capabilities, enabling users to create scalable and adaptable Plans. With Omnistrate, you can simplify your infrastructure-as-code workflows and ensure smooth, automated provisioning across various cloud environments and tenants.

Note

Support for Terraform Integration is available for AWS, GCP, Azure, OCI, and Nebius. You can define your Plan to use the corresponding stack depending on the cloud provider that is being deployed, making your service multi-cloud.

Warning

Omnistrate manages the Terraform state backend using Kubernetes secrets. Any custom backend configuration in your Terraform files (such as S3, GCS, or Azure Blob Storage) will be removed during deployment. Do not include a custom backend block — Omnistrate handles state management automatically.

Integrating Terraform on your Plan

Terraform stacks require a Plan specification file that defines your overall service topology on Omnistrate. Unlike Docker Compose deployments where the service specification can be embedded within the compose file, Terraform deployments must use a separate Plan specification file. A complete description of the Plan specification can be found on Plan Spec

Here is an example of a using Terraform to configure a SaaS Product:

provider "aws" {
  region = "{{ $sys.deploymentCell.region }}"
}

# Create a Security Group for RDS and ElastiCache
resource "aws_security_group" "rds_elasticache_sg" {
  name        = "e2e-rds-elasticache-security-group-{{ $sys.id }}"
  description = "Security group for RDS and ElastiCache instances"
  vpc_id      = "{{ $sys.deploymentCell.cloudProviderNetworkID }}"

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Adjust for appropriate security
  }

  ingress {
    from_port   = 11211  # Default Memcached port
    to_port     = 11211
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Adjust accordingly
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Create a DB Subnet Group for RDS
resource "aws_db_subnet_group" "rds_subnet_group" {
  name        = "e2e-rds-subnet-group-{{ $sys.id }}"
  description = "My RDS subnet group"

  subnet_ids = [
    "{{ $sys.deploymentCell.publicSubnetIDs[0].id }}",
    "{{ $sys.deploymentCell.publicSubnetIDs[1].id }}",
    "{{ $sys.deploymentCell.publicSubnetIDs[2].id }}"
  ]
}

# Create a Subnet Group for ElastiCache
resource "aws_elasticache_subnet_group" "elasticache_subnet_group" {
  name       = "e2e-elasticache-subnet-group-{{ $sys.id }}"
  description = "My ElastiCache subnet group"

  subnet_ids = [
    "{{ $sys.deploymentCell.publicSubnetIDs[0].id }}",
    "{{ $sys.deploymentCell.publicSubnetIDs[1].id }}",
    "{{ $sys.deploymentCell.publicSubnetIDs[2].id }}"
  ]
}

# Create RDS instances
resource "aws_db_instance" "example1" {
  identifier              = "e2e-instance-1-{{ $sys.id }}"
  engine                  = "mysql"
  instance_class          = "db.t3.micro"
  allocated_storage       = 20
  db_subnet_group_name    = aws_db_subnet_group.rds_subnet_group.name
  vpc_security_group_ids  = [aws_security_group.rds_elasticache_sg.id]
  username                = "admin"
  password                = "yourpassword"  
  parameter_group_name    = "default.mysql8.0"
  engine_version          = "8.0.37"
  skip_final_snapshot     = true

  depends_on = [
    aws_security_group.rds_elasticache_sg,
    aws_db_subnet_group.rds_subnet_group
  ]
}

resource "aws_db_instance" "example2" {
  identifier              = "e2e-instance-2-{{ $sys.id }}"
  engine                  = "mysql"
  instance_class          = "db.t3.micro"
  allocated_storage       = 20
  db_subnet_group_name    = aws_db_subnet_group.rds_subnet_group.name
  vpc_security_group_ids  = [aws_security_group.rds_elasticache_sg.id]
  username                = "admin"
  password                = "yourpassword"
  parameter_group_name    = "default.mysql8.0"
  engine_version          = "8.0.37"
  skip_final_snapshot     = true

  depends_on = [aws_db_instance.example1]
}

# Create ElastiCache Cluster for Memcached
resource "aws_elasticache_cluster" "example_memcached" {
  cluster_id             = "e2e-memcached-{{ $sys.id }}"
  engine                 = "memcached"
  node_type              = "cache.t3.micro"
  num_cache_nodes        = 2  # Adjust as needed

  subnet_group_name      = aws_elasticache_subnet_group.elasticache_subnet_group.name
  security_group_ids     = [aws_security_group.rds_elasticache_sg.id]

  depends_on = [
    aws_db_instance.example2  # Ensure ElastiCache is created after all RDS instances
  ]
}

Warning

Please ensure that the main file for your terraform stack contains the definition on the provider.

Terraform templates can be referenced from a GitHub repository, either private or public. One repository can contain multiple Terraform stacks, and can be referenced using a specific git reference (tag or branch) and a folder path within that repository. Omnistrate allows you to configure a reference to a Git repository and path where the Terraform stack is stored. When selecting a path for your repository, Omnistrate expects the entire Terraform definition to be under that folder structure.

You can also use a local Terraform artifact with omnistrate-ctl build. Use artifactRelativePath in the Plan spec and run CTL from the workspace root that contains that relative path.

Example workspace:

my-service/
  spec.yaml
  terraform/
    aws/
      main.tf
      variables.tf
      outputs.tf

Example local-artifact spec:

name: Terraform Local Artifact
deployment:
  hostedDeployment:
    awsAccountId: "<AWS_ACCOUNT_ID>"
    awsBootstrapRoleAccountArn: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-bootstrap-role"

services:
  - name: terraform
    internal: true
    terraformConfigurations:
      configurationPerCloudProvider:
        aws:
          terraformPath: /
          artifactRelativePath: terraform/aws

Build the Plan with CTL:

cd my-service
omnistrate-ctl build \
  --spec-type ServicePlanSpec \
  --file spec.yaml \
  --product-name "Terraform Local Artifact" \
  --release-as-preferred

There are two paths to configure:

  • artifactRelativePath tells CTL which local file or directory to upload.
  • terraformPath tells Omnistrate where to run OpenTofu or Terraform inside that uploaded content.

For example, if your Terraform module is in terraform/aws, you can upload that module directory directly:

artifactRelativePath: terraform/aws
terraformPath: /

Or you can upload its parent directory and point terraformPath at the module subdirectory:

artifactRelativePath: terraform
terraformPath: /aws

Use one Terraform source per cloud-provider entry: set either gitConfiguration or artifactRelativePath, not both. Prefer relative artifact paths such as terraform/aws; paths that escape the workspace root, such as ../terraform, are rejected.

Within the Terraform templates, the Omnistrate platform provides system parameters that can be used to reference to information about the current cluster, for instance:

  • $sys.deploymentCell.publicSubnetIDs[i].id
  • $sys.deploymentCell.privateSubnetIDs[i].id
  • $sys.deploymentCell.region
  • $sys.deploymentCell.cloudProviderNetworkID
  • $sys.deploymentCell.cidrRange

You can inject these values into your Terraform templates, and these values will be dynamically rendered during deployment.

In addition, the Omnistrate platform will automatically output all values defined in the Terraform templates' output block. You can use these outputs to inject into other deployment components, as shown in the example below:

Info

You can use system parameters to customize Terraform templates. A detailed list of system parameters be found on Build Guide / System Parameters.

Registering a Service using a Terraform stack

Terraform stacks are managed through a specification file that defines your overall service topology on Omnistrate. A complete description of the Plan specification can be found on Getting started / Plan Spec. Consider using Git tags to version the terraform stack and ensure consistency across Plan versions.

Here is an example of a SaaS Product that deploys Redis Clusters using a Helm chart.

Note

This example uses the legacy operatorCRDConfiguration.template and readinessConditions fields to show Terraform outputs flowing into an operator resource. For new operator-backed services, prefer systemWorkflows for lifecycle resources, readiness, failure conditions, and outputs. See Build from Kubernetes Operators.

Example:

name: Terraform Resources
deployment:
  byoaDeployment:
    awsAccountId: "<AWS_ACCOUNT_ID>"
    awsBootstrapRoleAccountArn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-bootstrap-role

  services:
    - name: terraform
      internal: true
      terraformConfigurations:
        configurationPerCloudProvider:
          aws:
            terraformPath: /artifacts/terraform/aws
            gitConfiguration:
              reference: refs/heads/main
              repositoryUrl: https://github.com/omnistrate-community/examples.git
    - name: CNPG
      dependsOn:
        - terraform
      operatorCRDConfiguration:
        template: |
          apiVersion: postgresql.cnpg.io/v1
          kind: Cluster
          metadata:
            name: cluster-example
            namespace: default # Should ignore this namespace
          spec:
            instances: 3
            storage:
              size: 1Gi

        readinessConditions:
          "$var._crd.status.phase": "Cluster in healthy state"
          "$var._crd.status.readyInstances": 3
          '$var._crd.status.conditions[?(@.type=="Ready")].status': "True"

        outputParameters:
          "Postgres Container Image": "$var._crd.status.image"
          "Status": "$var._crd.status.phase"
          "Topology": "$var._crd.status.topology"

        helmChartDependencies:
          - chartName: cloudnative-pg
            chartVersion: 0.22.1
            chartRepoName: cnpg
            chartRepoURL: https://cloudnative-pg.github.io/charts

You can register this spec using our CLI:

omnistrate-ctl build -f spec.yaml --name 'Multiple Resources' --release-as-preferred --spec-type ServicePlanSpec

# Example output shown below
 Successfully built service
Check the Plan result at: https://omnistrate.cloud/product-tier?serviceId=s-xxx&environmentId=se-xxx
Access your SaaS Product at: https://saasportal.instance-xxx.hc-xxx.us-east-2.aws.f2e0a955bb84.cloud/service-plans?serviceId=s-xxxx&environmentId=se-xxx

More Real-World Examples

Using Terraform outputs

Omnistrate platform will automatically output all values defined in the Terraform templates output block. You can use these outputs to inject into other deployment components.

For example, a resource can define output parameters on the stack

output "rds_endpoints_1" {
  value = aws_db_instance.example1.endpoint
}

output "rds_endpoints_2" {
  value = {
    endpoint: aws_db_instance.example2.endpoint
  }
  sensitive = true
}

output "elasticache_endpoint" {
  value = aws_elasticache_cluster.example_memcached.cache_nodes[0].address
}

and in the dependent resource we can reference to the terraform output properties:

{{ $terraformChild.out.rds_endpoints_1 }}
{{ $terraformChild.out.rds_endpoints_2.endpoint }}
{{ $terraformChild.out.elasticache_endpoint }}

If you want selected Terraform outputs to appear as exported fields on the Terraform resource in Omnistrate, declare them explicitly under requiredOutputs:

services:
  - name: terraformChild
    internal: true
    terraformConfigurations:
      configurationPerCloudProvider:
        aws:
          terraformPath: /terraform/aws
          gitConfiguration:
            reference: refs/heads/main
            repositoryUrl: https://github.com/your-org/infra-repo.git
          requiredOutputs:
            - key: rds_endpoints_1
              exported: true
            - key: elasticache_endpoint
              exported: true

For a deeper explanation of output mapping versus exported Terraform outputs, see Input Parameters and Output Mapping.

Additional permissions for Terraform

Using a terraform stack normally requires to define permissions to create, update and delete the entities involved. You can configure a custom policy / role that will be used when applying each Terraform stack defined as a resource in Omnistrate.

Custom Terraform permissions for BYOA Deployment Model

To configure permissions required to provision Terraform resources within BYOA Deployment Model, you can configure a Custom Terraform Policy. This feature is configured on the Plan level and Omnistrate will adjust onboarding scripts for your customers to include appropriate resources (depending on cloud provider).

  • For AWS, configuration is provider as a policy document. Omnistrate will create a dedicated AWS IAM role per-model when onboarding your customers. With each update, a new version of the cloud formation stack will be created.
  • For GCP, configuration is provided as a list of GCP IAM roles. Omnistrate will create a dedicated GCP IAM service account per-role with specified roles bound. GCP CLI commands will always reflect the latest configuration.
  • For Azure, configuration is provided as a list of RBAC roles. Omnistrate will create a single service principal with permissions merged across all service models. RBAC permissions are scoped to the subscription level. Entra roles can be bound by specifying the type as "Entra" and are bound to the root tenant level. Just like for the GCP, an Azure CLI onboarding script will always reflect the latest configuration.
  • For OCI, configuration is provided as a list of OCI IAM permission fragments under permissions.oci, such as manage queues and manage ons-topics. During onboarding, Omnistrate creates a Terraform user and renders these fragments into policy statements scoped to the account compartment created for the account config: Allow any-user to <permission> in compartment id <account_config_compartment> where request.principal.id='<terraform_user_ocid>'. During provisioning, this custom identity (AWS IAM role / GCP IAM service account) is used to modify Terraform resources within the Plan.

To configure custom terraform permissions using the service spec file, add the "CUSTOM_TERRAFORM_POLICY" feature such as:

features:
  CUSTOM_TERRAFORM_POLICY:
    policies:
      aws: |
      {
        "Statement": [
          {
            "Action": [
              "sqs:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
          }
        ]
      }
    roles:
      gcp:
        - name: roles/pubsub.admin
        - name: roles/cloudsql.admin
      azure:
        - name: "Network Contributor"
        - name: "Storage Account Contributor"
        - name: "Virtual Machine Contributor"
        - name: "Global Reader"
          type: "Entra"
    permissions:
      oci:
        - "manage queues"
        - "manage ons-topics"

In the example above, Terraform will be allowed to manage OCI Queues and OCI Notifications (ONS) topics.

To achieve the same effect using Omnistrate CTL, you can use the following command:

omctl service-plan enable-feature 'product name' 'Plan name' --feature CUSTOM_TERRAFORM_POLICY --feature-configuration-file ../path/to/config-file

With config file having the following content (AWS policy itself needs to be provided as JSON string):

{
 "policies": {
   "aws": "{\"Statement\":[{\"Action\": [\"sqs:*\"],\"Effect\": \"Allow\",\"Resource\": \"*\"}]}"
 },
 "roles": {
   "gcp": [
     {"name": "roles/pubsub.admin"},
     {"name": "roles/cloudsql.admin"}
   ],
   "azure": [
     {"name": "Network Contributor"},
     {"name": "Storage Account Contributor"},
     {"name": "Virtual Machine Contributor"},
     {"name": "Global Reader", "type": "Entra"}
   ] 
 },
 "permissions": {
   "oci": [
     "manage queues",
     "manage ons-topics"
   ]
 }
}

To disable this feature, use the following command:

omctl service-plan disable-feature 'product name' 'Plan name' --feature CUSTOM_TERRAFORM_POLICY 

Warning

Each time the custom terraform policy is updated, a new version of the onboarding script is generated. Your customers might need to re-run their account onboarding, otherwise they might not be able to provision your service. This requires updating their account onboarding CloudFormation stack (for AWS), or getting latest GCP CLI commands and running them (for GCP).

Custom Terraform execution identity for SaaS Provider hosted model

For SaaS Provider hosted deployments, Omnistrate manages Terraform execution identities for each cloud provider. For AWS, GCP, Azure, and OCI, the identity is auto-created — you only need to assign the permissions your Terraform stack requires. You do not need to specify the identity in the Plan spec.

  • AWS (auto-created): Omnistrate creates an IAM role named omnistrate-terraform-execution-role in your AWS account. Assign the permissions your Terraform stack needs to this role.
  • GCP (auto-created): Omnistrate creates a service account named omnistrate-tf-<org-id> in your GCP project. Assign the permissions your Terraform stack requires to this service account.
  • Azure (auto-created): Omnistrate creates a service principal named terraform-<org-id>-<subscription-id>. Assign the permissions your Terraform stack requires to this service principal.
  • OCI (auto-created): Omnistrate creates a user named <org-id>-terraform-user. Assign the permissions your Terraform stack requires to this user.
name: Multiple Resources
deployment:
  hostedDeployment:
    awsAccountId: "<AWS_ACCOUNT_ID>"
    awsBootstrapRoleAccountArn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-bootstrap-role

  services:
    - name: terraform
      internal: true
      terraformConfigurations:
        configurationPerCloudProvider:
          aws:
            terraformPath: /terraform
            gitConfiguration:
              reference: refs/heads/main
              repositoryUrl: https://github.com/omnistrate-community/examples.git
          gcp:
            terraformPath: /terraform
            gitConfiguration:
              reference: refs/heads/main
              repositoryUrl: https://github.com/omnistrate-community/examples.git

Nebius Terraform authentication

Nebius uses explicit service-account authentication. Configure the service-account credentials on the nebius Terraform entry:

services:
  - name: terraform
    internal: true
    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:

  • serviceAccountID, publicKeyID, and privateKeyPEM are all required for configurationPerCloudProvider.nebius.
  • privateKeyPEM can be inline, but prefer $secret.<name> so the PEM stays out of source control.
  • Omnistrate resolves $secret references before it hands the stack to Terraform.
  • Keep the Terraform source provider block minimal. Omnistrate injects the Nebius credential wiring at runtime; for example:
terraform {
  required_providers {
    nebius = {
      source = "terraform-provider.storage.eu-north1.nebius.cloud/nebius/nebius"
    }
  }
}

provider "nebius" {
  domain = "api.eu.nebius.cloud:443"
}
  • When Nebius auth is specified, Omnistrate uses it for that Terraform resource instead of the default host-cluster Terraform identity.

Control-Plane-Targeted Terraform

Some Terraform resources manage provider-side assets that should run in the Omnistrate control plane account instead of the data plane deployment cell. For those resources, set deploymentTarget.account: ControlPlane on the Terraform resource.

Example:

services:
  - name: controlPlaneInfra
    internal: true
    deploymentTarget:
      account: ControlPlane
    terraformConfigurations:
      configurationPerCloudProvider:
        aws:
          terraformPath: /terraform/control_plane
          gitConfiguration:
            reference: refs/heads/main
            repositoryUrl: https://github.com/your-org/infra-repo.git

Note

For control-plane-targeted Terraform, ensure that the appropriate execution identity exists in your cloud account. For AWS, GCP, Azure, and OCI, Omnistrate auto-creates the identity. Assign the permissions your Terraform stack requires to the identity.

Notes:

  • For control-plane-targeted Terraform, define only the cloud provider entry that actually executes the stack. A control-plane-targeted AWS stack does not need placeholder gcp, azure, or nebius entries.
  • There is no shared shorthand outside configurationPerCloudProvider; each provider entry is defined explicitly.
  • This is useful for shared control-plane resources such as DNS, registry, or other provider-side integrations that are not created inside each deployment cell.

AWS Terraform role

Omnistrate pre-creates the omnistrate-terraform-execution-role IAM role for Terraform executions during account setup. You do not need to create this role for AWS.

AWS requirements for Terraform role

The role uses the exact name omnistrate-terraform-execution-role. Omnistrate creates the role with the required Trusted Entity configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<AWS_ACCOUNT_ID>:root"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/dataplane-agent-iam-role-*"
                }
            }
        }
    ]
}

Info

Omnistrate creates the IAM role with the exact name omnistrate-terraform-execution-role.

Pre-created principals

Omnistrate will by default pre-create empty IAM principals for Terraform resources during account setup.

  • For AWS, IAM role omnistrate-terraform-execution-role will be created (this role starts with omnistrate- and has a trusted entity configuration)
  • For GCP, IAM service account omnistrate-tf-<org-id> will be created (this service account will have IAM binding to Omnistrate dataplane)
  • For Azure, service principal terraform-<org-id>-<subscription-id> will be created
  • For OCI, user <org-id>-terraform-user will be created

These principals are pre-created with necessary configuration (as described above). You can configure them with the necessary permissions for your Terraform stack. This pre-created-principal flow does not apply to Nebius Terraform resources; use the Nebius service-account fields shown above.

Terraform Variables Override with Inline File

Omnistrate supports overriding Terraform variables using inline file content through the variablesValuesFileOverride property. This feature allows you to define Terraform variable values directly within your Plan specification without requiring a separate .tfvars file in your repository.

The variablesValuesFileOverride property accepts a multi-line string containing Terraform variable definitions in the standard .tfvars format. You can use Omnistrate system parameters within these variable definitions to inject dynamic values during deployment.

Configuration Example

Here's an example of how to use the variablesValuesFileOverride feature:

name: Multiple Resources
deployment:
  byoaDeployment:
    awsAccountId: "<AWS_ACCOUNT_ID>"
    awsBootstrapRoleAccountArn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/omnistrate-bootstrap-role
    gcpProjectId: "<GCP_INFO>"
    gcpProjectNumber: "<GCP_INFO>"
    gcpServiceAccountEmail: "<GCP_INFO>"
    azureSubscriptionId: '<AZURE_INFO>'
    azureTenantId: '<AZURE_INFO>'

  services:
    - name: terraformChild
      internal: true
      terraformConfigurations:
        configurationPerCloudProvider:
          aws:
            terraformPath: /terraform
            variablesValuesFileOverride: |
              vpc_id = "{{ $sys.deploymentCell.cloudProviderNetworkID }}"
              region = "{{ $sys.deploymentCell.region }}"
              instance_type = "t3.medium"
              environment = "production"
              subnet_ids = [
                "{{ $sys.deploymentCell.publicSubnetIDs[0].id }}",
                "{{ $sys.deploymentCell.publicSubnetIDs[1].id }}"
              ]
            gitConfiguration:
              reference: refs/tags/12.0
              repositoryUrl: https://github.com/omnistrate-community/sample/TestKustomizeTemplate.git

Using System Parameters

You can inject Omnistrate system parameters into your variable values:

variablesValuesFileOverride: |
  # Network configuration from deployment cell
  vpc_id = "{{ $sys.deploymentCell.cloudProviderNetworkID }}"
  region = "{{ $sys.deploymentCell.region }}"
  availability_zones = [
    "{{ $sys.deploymentCell.region }}a",
    "{{ $sys.deploymentCell.region }}b"
  ]

  # Dynamic naming with system ID
  resource_prefix = "omnistrate-{{ $sys.id }}"

  # Subnet configuration
  public_subnets = [
    "{{ $sys.deploymentCell.publicSubnetIDs[0].id }}",
    "{{ $sys.deploymentCell.publicSubnetIDs[1].id }}"
  ]

  private_subnets = [
    "{{ $sys.deploymentCell.privateSubnetIDs[0].id }}",
    "{{ $sys.deploymentCell.privateSubnetIDs[1].id }}"
  ]

Tip

The variablesValuesFileOverride feature is particularly useful when you want to customize Terraform behavior on Omnistrate platform without modifying the underlying Terraform code.

Custom OpenTofu CLI Configuration

Omnistrate runs your Terraform stack with OpenTofu. Use the cliConfigFileOverride property to supply a custom OpenTofu CLI configuration file for a Terraform Resource — without baking it into your repository or a custom runner image.

The property accepts a multi-line string containing the CLI configuration file content. Omnistrate writes it into the Terraform workspace and points TF_CLI_CONFIG_FILE at it for every operation on the stack (init, plan, apply, destroy) for the cloud provider entry where you specify it. It works for both Git-based and artifact-based Terraform sources.

Common uses include provider installation mirrors and credentials for private module registries.

Example: Alternate Provider Mirrors for Specific Registries

The following configuration installs hashicorp/* providers from an internal network mirror while all other providers continue to install directly from their origin registries — useful when the public registry is unreachable from your deployment environment, or when provider binaries must come from a vetted internal source:

services:
  - name: terraformResource
    internal: true
    terraformConfigurations:
      configurationPerCloudProvider:
        aws:
          terraformPath: /terraform
          cliConfigFileOverride: |
            provider_installation {
              network_mirror {
                url     = "https://terraform-mirror.internal.example.com/providers/"
                include = ["registry.opentofu.org/hashicorp/*"]
              }
              direct {
                exclude = ["registry.opentofu.org/hashicorp/*"]
              }
            }
          gitConfiguration:
            reference: refs/heads/main
            repositoryUrl: https://github.com/your-org/infra-repo.git

With this configuration, tofu init resolves any provider matching registry.opentofu.org/hashicorp/* through the mirror at terraform-mirror.internal.example.com, and falls back to direct installation for everything else.

See the OpenTofu CLI configuration documentation for the full set of supported blocks, including credentials for private registries and oci_default_credentials for OCI-based mirrors.

Tip

Like variablesValuesFileOverride, the CLI configuration content supports Omnistrate system parameters, so mirror URLs or registry tokens can be injected dynamically at deployment time.

Note

CLI configuration files can contain credentials. Omnistrate stores the content on the Terraform Resource and redacts cliConfigFileOverride in API responses for users with read-only roles.

Dive Deeper

Now that you have a working Terraform-based SaaS Product, explore the build guides to extend your deployment: