Skip to main content

Module Reference Syntax

Git Source with Version

Always use version tags to ensure reproducible deployments:
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"
  # Module inputs...
}

Branch Reference

For testing or development:
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=main"
  # Module inputs...
}
Avoid using branch references in production. Always pin to specific version tags.

Commit SHA Reference

For maximum stability:
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=abc123def456"
  # Module inputs...
}

Common Patterns

Multi-Environment Setup

Organize modules across environments:
terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── production/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── modules/
    └── (local modules if needed)
Example dev environment:
# environments/dev/main.tf

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"
  
  vpc_name           = "${var.environment}-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = var.aws_region
  availability_zones = var.availability_zones
  
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
  
  # Dev: Single NAT for cost savings
  enable_nat_gateway = true
  single_nat_gateway = true
  
  tags = var.tags
}
Example production environment:
# environments/production/main.tf

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"
  
  vpc_name           = "${var.environment}-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = var.aws_region
  availability_zones = var.availability_zones
  
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
  
  # Production: NAT per AZ for HA
  enable_nat_gateway = true
  single_nat_gateway = false
  
  enable_s3_endpoint       = true
  enable_dynamodb_endpoint = true
  enable_flow_logs         = true
  
  tags = var.tags
}

Module Composition

Build complex infrastructure by composing modules:
# Network layer
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"
  # VPC configuration
}

# Compute layer
module "eks" {
  source = "[email protected]:opsnorth/terraform-modules.git//eks?ref=v1.0.0"
  
  cluster_name       = "app-cluster"
  private_subnet_ids = module.vpc.private_subnet_ids
}

# Data layer
module "rds" {
  source = "[email protected]:opsnorth/terraform-modules.git//rds?ref=v1.0.0"
  
  identifier = "app-db"
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
  
  allowed_security_group_ids = [module.eks.cluster_security_group_id]
}

# Secrets layer
module "vault" {
  source = "[email protected]:opsnorth/terraform-modules.git//vault?ref=v1.0.0"
  
  cluster_name      = module.eks.cluster_id
  oidc_provider_arn = module.eks.oidc_provider_arn
  oidc_provider_url = module.eks.oidc_provider_url
}

Using Module Outputs

Pass outputs between modules:
# VPC module outputs
output "vpc_outputs" {
  value = {
    vpc_id             = module.vpc.vpc_id
    private_subnet_ids = module.vpc.private_subnet_ids
    public_subnet_ids  = module.vpc.public_subnet_ids
  }
}

# Reference in other modules
module "eks" {
  source             = "[email protected]:opsnorth/terraform-modules.git//eks?ref=v1.0.0"
  private_subnet_ids = module.vpc.private_subnet_ids
}

# Reference in resources
resource "aws_security_group" "app" {
  vpc_id = module.vpc.vpc_id
  # Security group rules...
}

Variable Management

Using terraform.tfvars

Store environment-specific values:
# environments/production/terraform.tfvars

environment        = "production"
aws_region         = "us-east-1"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]

vpc_cidr             = "10.0.0.0/16"
public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

eks_version              = "1.34"
eks_node_instance_types  = ["m5.large"]
eks_node_desired_size    = 3

rds_instance_class      = "db.t3.medium"
rds_allocated_storage   = 100

tags = {
  Environment = "production"
  Team        = "platform"
  ManagedBy   = "terraform"
}

Using Variable Files

Load different configurations:
# Development
terraform apply -var-file="environments/dev/terraform.tfvars"

# Production
terraform apply -var-file="environments/production/terraform.tfvars"

Environment Variables

Use TF_VAR prefix:
export TF_VAR_environment="production"
export TF_VAR_aws_region="us-east-1"

terraform apply

State Management

Remote State Backend

Always use remote state for team collaboration:
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/vpc/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

State Isolation

Isolate state by layer or component:
state/
├── production/
│   ├── network/terraform.tfstate      # VPC
│   ├── compute/terraform.tfstate      # EKS
│   ├── data/terraform.tfstate         # RDS
│   └── secrets/terraform.tfstate      # Vault
└── dev/
    └── ...

Remote State Data Source

Reference outputs from other state files:
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "production/network/terraform.tfstate"
    region = "us-east-1"
  }
}

module "eks" {
  source             = "[email protected]:opsnorth/terraform-modules.git//eks?ref=v1.0.0"
  private_subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids
}

Tagging Strategy

Consistent Tags Across Modules

locals {
  common_tags = {
    Environment = var.environment
    Team        = "platform"
    ManagedBy   = "terraform"
    CostCenter  = "engineering"
    Project     = "shipyard"
  }
}

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"
  tags   = local.common_tags
}

module "eks" {
  source = "[email protected]:opsnorth/terraform-modules.git//eks?ref=v1.0.0"
  tags   = local.common_tags
}

Provider-Level Default Tags

provider "aws" {
  region = var.aws_region
  
  default_tags {
    tags = {
      Terraform   = "true"
      Environment = var.environment
      Project     = "shipyard"
    }
  }
}

Testing Modules

Plan Before Apply

Always review changes:
terraform plan -out=tfplan
terraform show tfplan
terraform apply tfplan

Target Specific Modules

Test individual modules:
# Plan only VPC changes
terraform plan -target=module.vpc

# Apply only EKS changes
terraform apply -target=module.eks
Using -target should be temporary. Always run a full plan/apply afterward to ensure consistency.

Validate Configuration

# Check syntax
terraform validate

# Format code
terraform fmt -recursive

# Validate with variables
terraform validate -var-file="terraform.tfvars"

Module Updates

Upgrading Module Versions

  1. Review changelog:
    git log v1.0.0..v1.1.0 -- vpc/
    
  2. Update source version:
    module "vpc" {
      source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.1.0"
    }
    
  3. Re-initialize:
    terraform init -upgrade
    
  4. Plan and review:
    terraform plan
    
  5. Apply changes:
    terraform apply
    

Pinning Versions in CI/CD

# .github/workflows/terraform.yml

name: Terraform

on:
  push:
    branches: [main]
  pull_request:

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.0
      
      - name: Terraform Init
        run: terraform init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      
      - name: Terraform Plan
        run: terraform plan -no-color
      
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve

Best Practices

Version Pinning

Always pin module versions using Git tags. Never use branches in production.

State Locking

Use DynamoDB for state locking to prevent concurrent modifications.

Environment Isolation

Separate state files and AWS accounts per environment.

Least Privilege IAM

Grant minimum required permissions. Use IAM roles over access keys.

Module Documentation

Document your usage patterns and custom configurations.

Change Review

Always review terraform plan output before applying.

Troubleshooting

Module Not Found

Error:
Error: Module not found
Solution:
# Ensure Git SSH access is configured
ssh -T [email protected]

# Re-initialize with upgrade flag
terraform init -upgrade

State Lock Errors

Error:
Error: Error acquiring the state lock
Solution:
# List locks
aws dynamodb scan --table-name terraform-state-lock

# Force unlock (use with caution)
terraform force-unlock <LOCK_ID>

Module Output Not Available

Error:
Error: Unsupported attribute
Solution: Check if the output exists in the module:
terraform output -module=vpc

VPC Module

Complete VPC infrastructure

EKS Module

Kubernetes cluster setup

State Module

Remote state backend

Infrastructure Guide

Complete deployment workflow

Build docs developers (and LLMs) love