Back to Articles

HashiCorp's Deprecated Best Practices Repo: A Time Capsule of Infrastructure-as-Code Evolution

[ View on GitHub ]

HashiCorp's Deprecated Best Practices Repo: A Time Capsule of Infrastructure-as-Code Evolution

Hook

The repository with 1,469 stars that HashiCorp explicitly tells you not to use might teach you more about infrastructure-as-code than most modern tutorials.

Context

In the early days of Terraform (circa 2015-2016), the infrastructure-as-code landscape was the Wild West. Teams struggled with basic questions: How do you organize Terraform code across multiple environments? Where should state files live? How do you avoid duplicating module code across AWS regions? HashiCorp created the best-practices repository as an opinionated answer to these questions, demonstrating patterns around their Atlas platform—a precursor to Terraform Cloud that promised centralized state management and workflow orchestration.

The repository emerged during a critical inflection point when companies were migrating from hand-crafted CloudFormation templates and Ansible playbooks to declarative infrastructure code. HashiCorp needed to show enterprises that Terraform could scale beyond single-region deployments. The patterns demonstrated here—separating Packer image building from Terraform provisioning, organizing code by provider and region, using modules to avoid duplication—were revolutionary at the time. Today, this deprecated repository stands as a historical artifact, officially abandoned in favor of the Terraform Module Registry, but it remains valuable for understanding how infrastructure-as-code thinking evolved.

Technical Insight

Cloud Providers

HashiCorp Atlas

Infrastructure Deployment

Image Building (Packer)

references

writes

builds

writes

reads state from

uses images

provisions via Atlas

deploys

deploys

state updates

state updates

Developer Code

Packer Templates

Machine Images/AMIs

Environment Configs

prod/staging/region

Reusable Modules

aws/google

Remote State

Orchestration Engine

AWS Resources

Google Cloud Resources

System architecture — auto-generated

The repository's architecture reveals an interesting organizational pattern that predates modern Terraform conventions. Code was structured hierarchically: provider (AWS, Google) → region (us-east-1, eu-west-1) → environment (production, staging). Each layer contained its own Terraform configurations that called reusable modules from a centralized modules directory. Here's what a typical structure looked like:

# providers/aws/us_east_1_prod/terraform.tfvars
atlas_environment = "production"
atlas_aws_region = "us-east-1"
site_ssl_cert = "${file(\"certs/site.crt\")}"
site_ssl_key = "${file(\"certs/site.key\")}"

# Calling a shared module
module "site" {
  source = "../../../modules/aws/site"
  
  atlas_environment = "${var.atlas_environment}"
  atlas_token = "${var.atlas_token}"
  region = "${var.atlas_aws_region}"
  vpc_cidr = "10.0.0.0/16"
}

The key architectural decision was tight coupling with Atlas for state management. Every configuration expected Atlas environment variables and used Atlas as the backend. This made sense in 2015—remote state backends were immature, and HashiCorp was building Atlas as an enterprise platform. But it also created the repository's eventual obsolescence when Atlas pivoted to become Terraform Cloud with fundamentally different workflows.

The Packer integration demonstrates another fascinating pattern: infrastructure as a two-phase process. Packer templates lived in a separate directory structure, building machine images tagged with Git commit SHAs. Terraform configurations then referenced these pre-built AMIs:

# Packer template creating base image
{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-d05e75b8",
    "instance_type": "t2.micro",
    "ami_name": "base-{{timestamp}}",
    "tags": {
      "commit": "{{user `git_commit`}}"
    }
  }]
}

# Terraform consuming the image
data "aws_ami" "base" {
  most_recent = true
  filter {
    name = "tag:commit"
    values = ["${var.git_commit}"]
  }
}

resource "aws_instance" "app" {
  ami = "${data.aws_ami.base.id}"
  instance_type = "t2.medium"
}

This separation of concerns—immutable infrastructure (Packer) from orchestration (Terraform)—remains a valid pattern today, though the implementation details have evolved significantly. Modern approaches use Terraform data sources more flexibly and typically integrate with CI/CD pipelines rather than Atlas's orchestration layer.

The module design reveals early thinking about reusability. Modules were organized by provider and resource type (aws/network, aws/compute) rather than by functional capability (vpc, kubernetes-cluster). Each module exposed numerous variables, following a kitchen-sink approach rather than sensible defaults:

variable "atlas_environment" {}
variable "atlas_token" {}
variable "region" {}
variable "vpc_cidr" {}
variable "public_subnet_cidrs" { type = "list" }
variable "private_subnet_cidrs" { type = "list" }
variable "availability_zones" { type = "list" }
# ... and many more

This verbosity reflected Terraform's early type system limitations and the lack of conventions around optional variables with defaults. Compare this to modern module design, which leverages complex types, validation blocks, and opinionated defaults to reduce configuration burden. The evolution from this pattern to today's Terraform Module Registry standards shows how the community learned to balance flexibility with usability.

Gotcha

The repository's deprecation notice is unambiguous, but the real gotcha is more subtle: many of the organizational patterns demonstrated here have been superseded by features that didn't exist when the code was written. The provider-region-environment directory structure made sense before Terraform workspaces existed. The heavy reliance on modules made sense before the Terraform Registry provided a centralized discovery mechanism. The environment variable configuration approach made sense before Terraform Cloud offered native variable management.

Attempting to use this code today will immediately surface breaking changes. The HCL syntax uses Terraform 0.11 string interpolation ("${var.name}") throughout, which modern Terraform versions deprecate in favor of direct references (var.name). The Atlas backend configuration is completely non-functional since Atlas no longer exists in its original form. Perhaps most importantly, the security assumptions are outdated—SSL certificates stored in the repository, tokens passed through environment variables, and no consideration for secrets management tools that are now standard practice. The repository serves as a reminder that infrastructure-as-code best practices are not static; they evolve with tooling, security understanding, and operational experience.

Verdict

Use if: You're researching the historical evolution of infrastructure-as-code patterns, maintaining legacy Atlas-based infrastructure that follows these patterns, or teaching a workshop about how NOT to organize modern Terraform projects (seriously—showing deprecated patterns can be pedagogically valuable). This repository is a time capsule worth studying for perspective on how far the ecosystem has progressed. Skip if: You're building anything new or looking for production-ready patterns. HashiCorp explicitly recommends the Terraform Module Registry instead, and for good reason. Modern alternatives like the terraform-aws-modules organization or Gruntwork's infrastructure library provide maintained, secure, and idiomatically correct patterns using current Terraform features. Your time is better spent learning current best practices than adapting deprecated ones, unless historical understanding is specifically your goal.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/hashicorp-best-practices.svg)](https://starlog.is/api/badge-click/developer-tools/hashicorp-best-practices)