Skip to content

How to manage multiple environments with OpenTofu

How-to · Updated May 2026

Coming from another cloud?

▸AWS·Terraform Workspaces

This Quake AI feature maps to AWS’s Terraform Workspaces.

▸Google Cloud·Terraform Environments

This Quake AI feature maps to Google Cloud’s Terraform Environments.

How to manage multiple environments with OpenTofu

Most production workloads need at least two environments: one for development and testing, one for production. This guide covers two approaches to managing multiple environments on Quake AI with OpenTofu: variable files and directory structure.

Prerequisites#

  • OpenTofu or Terraform installed
  • Quake AI credentials configured (see How to get started with IaC)
  • Familiarity with OpenTofu variables and modules

Approach 1: Variable files per environment#

The most straightforward approach uses a single set of .tf files with different .tfvars files for each environment.

Project structure#

project/
  main.tf
  variables.tf
  outputs.tf
  backend.tf
  envs/
    dev.tfvars
    staging.tfvars
    production.tfvars

Define variables#

In variables.tf, parameterize everything that differs between environments:

HCL
variable "environment" {
  type        = string
  description = "Environment name (dev, staging, production)"
}

variable "instance_count" {
  type    = number
  default = 1
}

variable "flavor_name" {
  type        = string
  description = "Compute flavor for instances"
}

variable "network_cidr" {
  type        = string
  description = "CIDR block for the private network"
}

Create per-environment variable files#

envs/dev.tfvars:

HCL
environment    = "dev"
instance_count = 1
flavor_name    = "s1a.small"
network_cidr   = "10.0.1.0/24"

envs/production.tfvars:

HCL
environment    = "production"
instance_count = 3
flavor_name    = "m2a.large"
network_cidr   = "10.0.10.0/24"

Use environment-specific state#

Configure the backend to use a different state key per environment. Pass the key during initialization:

bash
tofu init -backend-config="key=dev/terraform.tfstate"

Or use a backend configuration file per environment:

HCL
# envs/dev.backend.hcl
key = "dev/terraform.tfstate"
bash
tofu init -backend-config=envs/dev.backend.hcl

Apply to a specific environment#

bash
tofu plan -var-file=envs/dev.tfvars
tofu apply -var-file=envs/dev.tfvars

For production:

bash
tofu plan -var-file=envs/production.tfvars
tofu apply -var-file=envs/production.tfvars

Resource naming#

Use the environment variable in resource names to avoid collisions:

HCL
data "openstack_images_image_v2" "ubuntu" {
  name        = "Ubuntu-24.04"
  most_recent = true
}

resource "openstack_compute_instance_v2" "app" {
  count       = var.instance_count
  name        = "${var.environment}-app-${count.index}"
  flavor_name = var.flavor_name

  block_device {
    uuid                  = data.openstack_images_image_v2.ubuntu.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 10
    delete_on_termination = true
  }

  network {
    name = openstack_networking_network_v2.main.name
  }
}

Approach 2: Directory-per-environment#

For teams that prefer complete isolation, use a separate directory for each environment. Each directory has its own .tf files and state.

Project structure (Approach 2: Directory-per-environment)#

project/
  modules/
    app/
      main.tf
      variables.tf
      outputs.tf
  environments/
    dev/
      main.tf
      backend.tf
    staging/
      main.tf
      backend.tf
    production/
      main.tf
      backend.tf

Shared module#

Put reusable infrastructure in modules/app/:

HCL
# modules/app/main.tf
variable "environment" { type = string }
variable "instance_count" { type = number }
variable "flavor_name" { type = string }

data "openstack_images_image_v2" "ubuntu" {
  name        = "Ubuntu-24.04"
  most_recent = true
}

resource "openstack_compute_instance_v2" "app" {
  count       = var.instance_count
  name        = "${var.environment}-app-${count.index}"
  flavor_name = var.flavor_name

  block_device {
    uuid                  = data.openstack_images_image_v2.ubuntu.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 10
    delete_on_termination = true
  }

  network {
    name = "PublicEphemeral"
  }
}

Environment entry points#

Each environment calls the module with its own values:

HCL
# environments/dev/main.tf
module "app" {
  source         = "../../modules/app"
  environment    = "dev"
  instance_count = 1
  flavor_name    = "s1a.small"
}
HCL
# environments/production/main.tf
module "app" {
  source         = "../../modules/app"
  environment    = "production"
  instance_count = 3
  flavor_name    = "m2a.large"
}

Apply per environment#

bash
cd environments/dev
tofu init && tofu apply

cd ../production
tofu init && tofu apply

Which approach to use#

FactorVariable filesDirectory-per-environment
State isolationSame config, separate state keysFully separate state and config
Drift between environmentsEnvironments stay in sync by defaultCan diverge intentionally
ComplexityLower (one set of files)Higher (duplicate entry points)
CI/CD integrationPass -var-file flagChange directory
Best forSmall-to-medium teams, similar environmentsLarge teams, environments with different architectures

For most Quake AI projects, variable files (Approach 1) are sufficient. Use directory-per-environment when production and development have fundamentally different resource configurations.

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 25.05.2026

Was this page helpful?