In the previous subchapter, we saw that workspaces are not the best way to separate serious environments. Now we’ll look at the community-recommended strategy: organizing infrastructure in separate directories by environment. It’s more explicit, safer, and much easier to understand. This is how most professional teams manage their environments.

The idea: one folder per environment

Instead of a single codebase that changes behavior depending on the workspace, you have one folder for each environment, each with its own configuration and its own state:

my-infrastructure/
 ├── modules/                  ← reusable modules (Chapter 18)
 │    ├── network/
 │    ├── servers/
 │    └── database/
 │
 └── environments/
      ├── dev/                 ← development environment
      │    ├── main.tf
      │    └── terraform.tfvars
      ├── stg/                 ← staging environment
      │    ├── main.tf
      │    └── terraform.tfvars
      └── prod/                ← production environment
           ├── main.tf
           └── terraform.tfvars

Each environment folder uses the same modules (to avoid duplicating logic), but passes them different values. This way, “what is built” is in the modules (shared), and “with what size and configuration” is in each environment (separate).

How duplication is avoided: modules

This is where everything you learned in Chapter 18 shines. The infrastructure logic lives only once in the modules. Each environment simply calls those modules with its own values:

# environments/dev/main.tf
module "servers" {
  source         = "../../modules/servers"
  instance_type  = "t3.micro"     # small and cheap for dev
  count          = 1
}
# environments/prod/main.tf
module "servers" {
  source         = "../../modules/servers"
  instance_type  = "t3.large"     # large and robust for production
  count          = 5
}

Same servers module, two environments, different configurations. If you improve the module, both environments benefit, but each keeps its own size and configuration. There’s no duplication of logic.

Why this is better than workspaces

This strategy solves the problems we saw in subchapter 19.1:

  1. Clear and strong separation

Each environment is an independent folder with its own state (remember to configure a different backend per environment, Chapter 11 and subchapter 20.1). Production and development are truly separated: an error in one folder doesn’t affect the others.

  1. Hard to mix up environments

To work in production, you have to physically enter the prod/ folder and run Terraform there. It’s not a subtle workspace select that can be forgotten: it’s an obvious directory change. This greatly reduces the risk of applying something in the wrong environment.

cd environments/prod        # you are VERY aware of where you are
terraform apply             # you apply in production, no ambiguity

  1. Total visibility

Looking at the folder structure, you can see at a glance which environments exist and what each contains. Open environments/prod/main.tf and you know exactly what’s in production. It’s transparent and easy to audit.

  1. Flexibility

If production needs components that development doesn’t have (for example, database replicas or extra backups), you simply add them in prod/main.tf without affecting the other environments. Each environment can diverge as needed, without convoluted conditionals.

Analogy: workspaces were like having a single house and “changing the decor” depending on who visits. The directory strategy is like having separate houses for each purpose: one to live in (production), another to experiment in (development). They are physically separated, you don’t confuse one house for another, and you can renovate one without touching the other.

The role of .tfvars files

You may have noticed a terraform.tfvars file in each environment. This is where you put the variable values specific to that environment (we’ll cover this in detail in subchapter 19.4). This way, the configuration of each environment (sizes, names, quantities) is separated and clear, without touching the code.

The small downside: some repetition

This strategy has a cost: there is some repetition in the main.tf files of each environment (the module calls look similar). In projects with many environments, this repetition can become annoying. That’s what a tool called Terragrunt is for, which reduces this repetition and we’ll see in the next subchapter.

What you should remember

  • The recommended strategy for serious environments is directory separation: one folder per environment (dev/, stg/, prod/), each with its own configuration and own state.
  • The logic lives only once in the modules (Chapter 18); each environment calls those modules with different values (sizes, quantities), avoiding logic duplication.
  • Advantages over workspaces: clear and strong separation, hard to mix up environments (you physically enter the folder), total visibility, and flexibility for each environment to diverge.
  • Like separate houses for each purpose, instead of redecorating the same house.
  • Downside: some repetition between environments, which Terragrunt helps reduce (next subchapter).

In the next subchapter we’ll look at Terragrunt, a tool that keeps your environment configurations DRY (without repetition).

Cloud, AWS & Terraform — From Zero to Expert

Chapter 1 · What is cloud computing

Chapter 2 · The cloud market and major providers

Chapter 3 · Regions, availability zones and edge

Chapter 4 · Compute: EC2

Chapter 5 · Storage: S3

Chapter 6 · Networking: VPC

Chapter 7 · Identity and access: IAM

Chapter 8 · Managed databases

Chapter 9 · Why Infrastructure as Code

Chapter 10 · HCL: the Terraform language

Chapter 11 · Providers and state

Chapter 12 · Your first real infrastructure in Terraform

Chapter 13 · Load balancing and auto scaling

Chapter 14 · Serverless with Lambda

Chapter 15 · Messaging and events

Chapter 16 · Content delivery and DNS

Chapter 17 · Containers on AWS

Chapter 18 · Modules: reuse and composition

Chapter 19 · Workspaces and environment management

Chapter 20 · Remote backends and locking

Chapter 21 · Infrastructure testing

Chapter 22 · Terraform in CI/CD

Chapter 23 · Defense in depth

Chapter 24 · Observability: logs, metrics and traces

Chapter 25 · Cost optimization

Chapter 26 · High availability and disaster recovery

Chapter 27 · AWS Well-Architected Framework

Chapter 28 · Serverless architectures at scale

Chapter 29 · Data platforms on AWS

Chapter 30 · Multi-account and landing zones

Chapter 31 · Platform Engineering and Internal Developer Platform

Chapter 32 · Relevant AWS certifications

Chapter 33 · Projects to consolidate what you've learned

Chapter 34 · Resources and community

© Copyright 2024. All rights reserved