# How to manage multiple environments with OpenTofu

Source: https://docs.quake.ai/docs/automation/how-to/multi-environment
Markdown: https://docs.quake.ai/docs/automation/how-to/multi-environment.md

---

# 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](/docs/automation/how-to/getting-started-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.


Every Quake AI flavor is volume-backed (the flavor's ephemeral disk is zero), so an `openstack_compute_instance_v2` resource requires a `block_device` block instead of a bare `image_name`. The attached volume becomes the boot disk. This applies to both approaches below.


### Project structure

```text
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)

```text
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

| Factor | Variable files | Directory-per-environment |
|---|---|---|
| **State isolation** | Same config, separate state keys | Fully separate state and config |
| **Drift between environments** | Environments stay in sync by default | Can diverge intentionally |
| **Complexity** | Lower (one set of files) | Higher (duplicate entry points) |
| **CI/CD integration** | Pass `-var-file` flag | Change directory |
| **Best for** | Small-to-medium teams, similar environments | Large 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

- [How to manage OpenTofu state with Quake AI S3](/docs/automation/how-to/state-management)
- [How to integrate OpenTofu with CI/CD](/docs/automation/how-to/cicd-integration)
- [Infrastructure Templates](/resources/iac-templates)
