The Developer's Guide to HCL, Part 4: Best Practices

Write clean, scalable HCL. Learn best practices for project structure, module design, state management, code formatting, validation, and testing your IaC.

Writing high-quality HCL is essential for creating infrastructure configurations that are functional, understandable, and scalable. This section outlines best practices for structuring projects, managing state, writing cleaner code, and testing. Part 1 covered the basics, Part 2 covered syntax, and Part 3 outlined common issues.

Structuring HCL Projects and Modules

A well-organized project is a maintainable project.

  • Standard File Layout: Adopt a consistent file structure.
    • main.tf: Core resource definitions.
    • variables.tf: Input variable declarations.
    • outputs.tf: Output value definitions.
    • versions.tf: Terraform and provider version constraints.
    • README.md: Essential documentation for your module.
  • Module Design Principles: Modules are the key to reusability.
    • Keep Modules Focused: A module should do one thing well (e.g., manage a VPC, deploy a database).
    • Use Clear Inputs/Outputs: Define variables and outputs clearly with types and descriptions.
    • Avoid Hardcoding: Parameterize everything that might change between environments.
    • Don't Hardcode Environment Logic: A module should be environment-agnostic. Pass in environment-specific configuration via variables rather than using conditional logic like if var.env == "prod" inside the module.
  • Managing Multiple Environments (Dev/Staging/Prod):
    • Directory-Based Separation: This is the most common and recommended approach. Create a separate directory for each environment. Each directory can then call shared, reusable modules with its own specific variable values (.tfvars) and backend state configuration. This provides strong isolation.
    • Avoid Terraform Workspaces for Environments: Workspaces are better for feature branches or temporary instances, not for strong isolation between long-lived environments like dev and prod. Using separate directories is safer. Also see Terraform Workspaces for Environment Management

For a deeper dive, see How to Structure Terraform.

Terraform State Management Strategies

Terraform state is the source of truth for your managed infrastructure. Protecting it is critical.

  • Always Use Remote State: For any collaborative or production project, store your state file remotely. Local state is only for solo experiments.
    • Benefits: Collaboration, reliability, and most importantly, state locking.
    • Common Backends: AWS S3 with a DynamoDB table for locking, Azure Blob Storage, Google Cloud Storage, or Terraform Cloud.
  • State Locking is Mandatory: Remote backends provide locking mechanisms to prevent multiple people from running terraform apply at the same time and corrupting the state file. If your backend doesn't support locking, don't use it.
  • Split Your State: Do not use a single, monolithic state file for all your infrastructure. A large state file is slow and a single error has a massive blast radius.
    • Splitting Strategy: Decompose your state by environment, component, or service. For example, have separate state files for your networking, your databases, and your application services.
    • Benefits: Faster plans/applies, reduced blast radius for errors, and better security boundaries.

Writing Cleaner HCL: Formatting and Validation

Clean code is trustworthy code.

  • terraform fmt: Run this command automatically to enforce a canonical, consistent style. Integrate it into a pre-commit hook so code is always formatted before it enters version control.
  • terraform validate: Run this command to check for syntax errors and basic consistency. It's fast because it doesn't access the network. This should be the first step in your CI/CD pipeline.
  • Use Clear Naming Conventions: Your resource, variable, and output names should be descriptive and consistent. aws_s3_bucket.customer_billing_reports_prod is much better than aws_s3_bucket.bucket.

Testing HCL Configurations

Testing Infrastructure as Code is crucial for reliability.

  • Static Analysis: This is your first line of defense.
    • terraform fmt and terraform validate are basic static analysis tools.
    • Use third-party tools like TFLint to check for provider-specific best practices, find potential errors, and enforce coding standards.
  • terraform plan as a Dry Run: The plan is an essential preview, but it is not a test. It shows intent but doesn't guarantee success.
  • Integration Testing: For important modules, write automated tests that deploy real infrastructure.
    • Terratest: A Go library from Gruntwork is the industry standard for this. It allows you to write tests that run terraform apply, make assertions about the created infrastructure (e.g., "Is port 443 open?"), and then run terraform destroy. This provides the highest level of confidence that your module works as expected.

Next up, a recap:

The Developer’s Guide to HCL, Part 5: Recap & Further Reading
Recap HCL’s strengths for IaC. Get official links to documentation for HCL, Terraform, Packer, Nomad, and Vault to continue your learning journey.

Key Sources