๐Ÿถ
Terraform

Terraform Repo Structure: Best Practices for Subfolders

By Filip on 10/07/2024

Learn industry best practices for organizing your Terraform code with a clear and efficient subfolder structure for managing multiple environments and components.

Terraform Repo Structure: Best Practices for Subfolders

Table of Contents

Introduction

This article outlines a structured approach to organizing your Terraform projects for better maintainability and scalability. We'll establish a clear directory structure that separates concerns like environments, reusable modules, and deployment stages. This approach promotes code reusability, simplifies environment management, and enhances collaboration in infrastructure-as-code projects.

Step-by-Step Guide

Create a directory for your project. Inside, create three more directories: environments, modules, and stages.

The environments directory will hold configurations for different environments like production and staging. Each environment will have its own subdirectory.

The modules directory will contain reusable Terraform modules. For example, you might have a module for creating a VPC or a module for deploying a specific application.

The stages directory will hold configurations for different stages of your infrastructure deployment. This could include stages like network, database, and application. Each stage will have its own subdirectory.

Inside each environment directory, create subdirectories for each stage you want to deploy in that environment. For example, the production environment might have subdirectories for network, database, and application.

Inside each stage directory, create a main.tf file and any other necessary files for that stage. The main.tf file will contain the Terraform code to deploy the resources for that stage.

Use Terraform modules to encapsulate reusable infrastructure components. Define your modules in the modules directory and call them from your stage configurations.

When defining your Terraform code, use relative paths to reference modules and other files within your project. This will make your code more portable and easier to maintain.

Use a consistent naming convention for your files, directories, and Terraform resources. This will make your code easier to read and understand.

Use a remote backend for storing your Terraform state. This will allow you to collaborate with others on your infrastructure code and ensure that your state is stored securely.

Use Terraform workspaces to manage different environments or deployments. This will allow you to keep your code and state separate for each environment.

Use a version control system like Git to track changes to your Terraform code. This will allow you to roll back changes if necessary and collaborate with others on your infrastructure code.

Code Example

This Terraform code defines infrastructure for different environments (production and staging) using a modular approach. It includes modules for common components like VPCs and leverages remote state storage in S3. The code is structured to promote reusability and maintainability, allowing for consistent deployments across environments.

โ”œโ”€โ”€ environments
โ”‚   โ”œโ”€โ”€ production
โ”‚   โ”‚   โ”œโ”€โ”€ network
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ main.tf
โ”‚   โ”‚   โ”œโ”€โ”€ database
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ main.tf
โ”‚   โ”‚   โ””โ”€โ”€ application
โ”‚   โ”‚       โ””โ”€โ”€ main.tf
โ”‚   โ””โ”€โ”€ staging
โ”‚       โ”œโ”€โ”€ network
โ”‚       โ”‚   โ””โ”€โ”€ main.tf
โ”‚       โ””โ”€โ”€ database
โ”‚           โ””โ”€โ”€ main.tf
โ”œโ”€โ”€ modules
โ”‚   โ”œโ”€โ”€ vpc
โ”‚   โ”‚   โ””โ”€โ”€ main.tf
โ”‚   โ””โ”€โ”€ aws_s3_bucket
โ”‚       โ””โ”€โ”€ main.tf
โ””โ”€โ”€ stages
    โ”œโ”€โ”€ network
    โ”‚   โ””โ”€โ”€ main.tf
    โ”œโ”€โ”€ database
    โ”‚   โ””โ”€โ”€ main.tf
    โ””โ”€โ”€ application
        โ””โ”€โ”€ main.tf

environments/production/network/main.tf

# Configure the AWS Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

# Use the VPC module
module "vpc" {
  source = "../../../modules/vpc"

  vpc_cidr = "10.0.0.0/16"
}

environments/production/database/main.tf

# Configure the AWS Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

# Use the VPC module
module "vpc" {
  source = "../../../modules/vpc"

  vpc_cidr = "10.0.0.0/16"
}

# Create a security group for the database
resource "aws_security_group" "db_sg" {
  name = "database_sg"
  vpc_id = module.vpc.vpc_id

  ingress {
    from_port = 5432
    to_port = 5432
    protocol = "tcp"
    cidr_blocks = [module.vpc.vpc_cidr]
  }

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

modules/vpc/main.tf

# Create a VPC
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support = true

  tags = {
    Name = "main-vpc"
  }
}

# Create a subnet
resource "aws_subnet" "main" {
  vpc_id = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "us-west-2a"

  tags = {
    Name = "main-subnet"
  }
}

# Create an internet gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "main-igw"
  }
}

stages/network/main.tf

# Configure the AWS Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
  backend "s3"{
    bucket = "your-bucket-name"
    key = "terraform.tfstate"
    region = "us-west-2"
  }
}

# Use the VPC module
module "vpc" {
  source = "../../modules/vpc"

  vpc_cidr = "10.0.0.0/16"
}

Explanation:

  • Project Structure: The code is organized into environments, modules, and stages, promoting modularity and reusability.
  • Modules: The vpc module encapsulates VPC creation logic, making it reusable across environments and stages.
  • Relative Paths: Terraform configurations use relative paths (../../modules/vpc) to reference modules, enhancing portability.
  • Remote Backend: The backend "s3" configuration enables storing Terraform state in an S3 bucket, facilitating collaboration and state management.
  • Workspaces: While not explicitly shown, you can use Terraform workspaces to manage different environments (e.g., terraform workspace select production) to isolate state and configurations.
  • Version Control: It's highly recommended to use a version control system like Git to track changes and collaborate on your Terraform code.

To use this structure:

  1. Create Directories: Create the directory structure as shown.
  2. Populate Files: Copy the code snippets into their respective files.
  3. Customize: Modify variables and configurations (e.g., vpc_cidr, AWS region) to match your requirements.
  4. Initialize Terraform: Run terraform init in each environment's stage directory (e.g., environments/production/network) to initialize Terraform.
  5. Deploy: Use terraform apply to deploy your infrastructure.

This structure provides a solid foundation for managing your infrastructure as code with Terraform, promoting modularity, reusability, and maintainability. Remember to adapt it to your specific project needs and best practices.

Additional Notes

General:

  • Flexibility: This structure is a starting point. Adapt it to your project's complexity and specific needs.
  • Documentation: Maintain README files at different levels (root, modules, environments) to explain the structure and usage.
  • Automation: Use scripting or tools like Terragrunt to automate deployments across multiple environments and stages.

Environments:

  • Environment Variables: Use environment variables or configuration files to manage environment-specific settings (e.g., AWS credentials, region).
  • Separate State: Each environment should ideally have its own remote state file to prevent conflicts.

Modules:

  • Versioning: Version your modules (using Git tags or a registry) to manage dependencies and ensure consistent deployments.
  • Testing: Implement module testing using tools like Terratest to validate their functionality.

Stages:

  • Dependencies: Define dependencies between stages using depends_on in your Terraform code to ensure resources are created in the correct order.
  • Modularity: Break down stages into smaller, manageable units if a stage becomes too complex.

Security:

  • Secrets Management: Never store sensitive information (API keys, passwords) directly in your Terraform code. Use a secrets management solution like HashiCorp Vault.
  • IAM Roles: Utilize IAM roles for Terraform deployments instead of storing AWS credentials directly.

Best Practices:

  • Keep it DRY: Follow the "Don't Repeat Yourself" principle. Use modules and variables to avoid code duplication.
  • Code Reviews: Implement code reviews to ensure code quality, security, and adherence to best practices.
  • Continuous Integration/Continuous Deployment (CI/CD): Integrate your Terraform code with a CI/CD pipeline for automated testing and deployments.

Additional Considerations:

  • Terraform Cloud/Enterprise: Consider using Terraform Cloud or Enterprise for enhanced collaboration, state management, and governance features.
  • Alternative Tools: Explore alternative tools like Terragrunt or Spacelift for managing complex Terraform deployments and workflows.

Summary

This article outlines a best-practice structure for organizing Terraform projects to enhance modularity, maintainability, and collaboration.

Key Directory Structure:

  • environments: Holds environment-specific configurations (e.g., production, staging). Each environment has its own subdirectory.
  • modules: Contains reusable Terraform modules for common infrastructure components (e.g., VPC, application deployment).
  • stages: Organizes infrastructure deployment into logical stages (e.g., network, database, application). Each stage has its own subdirectory.

Workflow:

  1. Environment Setup: Create subdirectories within each environment (e.g., production/network, staging/database) to reflect the stages deployed in that environment.
  2. Stage Configuration: Within each stage directory, create a main.tf file and any other necessary files. The main.tf file contains the Terraform code for deploying resources in that stage.
  3. Module Utilization: Leverage Terraform modules defined in the modules directory to encapsulate reusable infrastructure components. Reference these modules using relative paths within your stage configurations.

Best Practices:

  • Relative Paths: Use relative paths for referencing modules and files to enhance code portability and maintainability.
  • Naming Conventions: Implement consistent naming conventions for files, directories, and Terraform resources to improve code readability and understanding.
  • Remote Backend: Utilize a remote backend for storing Terraform state, enabling collaboration and secure state management.
  • Terraform Workspaces: Employ Terraform workspaces to manage different environments or deployments, keeping code and state separate.
  • Version Control: Track changes to your Terraform code using a version control system like Git to facilitate rollback capabilities and collaborative development.

Conclusion

By adhering to these principles and adapting the structure to your specific needs, you can establish a robust and scalable foundation for managing your infrastructure with Terraform. This approach not only enhances code organization but also promotes collaboration, simplifies environment management, and ensures the long-term maintainability of your infrastructure-as-code projects.

References

Were You Able to Follow the Instructions?

๐Ÿ˜Love it!
๐Ÿ˜ŠYes
๐Ÿ˜Meh-gical
๐Ÿ˜žNo
๐ŸคฎClickbait