Claude
Skills
Sign in
Back

terraform-aws

Included with Lifetime
$97 forever

This skill should be used when the user asks to "create terraform configuration", "deploy static site", "set up cloudfront", "configure route53", "create lambda function", "ssl certificate", or mentions S3 website hosting, CDN, serverless, JAMstack, or static site infrastructure.

Cloud & DevOps

What this skill does


# Terraform for Serverless & Static Sites

Focused guidance for creating serverless and static site infrastructure on AWS using Terraform.

This skill covers: **Route53**, **S3**, **CloudFront**, **ACM**, and **Lambda**.

## Overview

This skill provides best practices for managing serverless and static site infrastructure as code using Terraform. It covers the essential AWS services for JAMstack applications, static websites, and serverless functions.

## Core Principles

### Infrastructure as Code Fundamentals

Follow these foundational principles when writing Terraform:

1. **Declarative Configuration**: Define desired state, not procedural steps
2. **Immutable Infrastructure**: Replace rather than modify resources
3. **Version Control**: All Terraform code in Git with proper branching
4. **Modularity**: Reusable modules for common patterns
5. **Security First**: Always encrypt data, use HTTPS, follow least privilege

### File Organization

Structure Terraform projects consistently. Modern projects typically use a locals-first approach:

```
static-site-infra/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── locals.tf
│   │   ├── outputs.tf
│   │   ├── versions.tf
│   │   └── backend.tf
│   ├── staging/
│   └── prod/
├── modules/
│   ├── static-site/
│   ├── lambda-function/
│   └── cdn/
├── .terraform.lock.hcl
└── README.md
```

**File conventions:**
- `main.tf` - Primary resource definitions
- `variables.tf` - Input variable declarations (minimal, project-level only)
- `locals.tf` - Environment-specific configuration and computed values
- `outputs.tf` - Output value declarations
- `versions.tf` - Terraform and provider version constraints
- `backend.tf` - Remote state configuration

## State Management

### Remote State Configuration

Always use remote state for team environments:

```hcl
# backend.tf
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "static-site/prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}
```

### State Management Best Practices

1. **Enable Encryption**: Always encrypt state files (contains secrets like Lambda env vars)
2. **State Locking**: Use DynamoDB for state locking (prevents conflicts)
3. **Separate States**: Different state files per environment/site
4. **Backup Strategy**: Enable S3 versioning on state bucket
5. **Access Control**: Restrict state bucket access via IAM

## Security Best Practices

### Secrets Management

Never hardcode credentials or sensitive data:

```hcl
# ❌ WRONG
resource "aws_lambda_function" "api" {
  environment {
    variables = {
      API_KEY = "sk-1234567890"  # NEVER DO THIS
    }
  }
}

# ✅ CORRECT - Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "api_key" {
  secret_id = "prod/api/key"
}

resource "aws_lambda_function" "api" {
  environment {
    variables = {
      API_KEY_ARN = data.aws_secretsmanager_secret_version.api_key.arn
    }
  }
}

# ✅ CORRECT - Use variables (set via environment)
variable "api_key" {
  description = "API key for external service"
  type        = string
  sensitive   = true
}

resource "aws_lambda_function" "api" {
  environment {
    variables = {
      API_KEY = var.api_key
    }
  }
}
```

### Security Checklist

- [ ] No hardcoded credentials or secrets
- [ ] Sensitive variables marked with `sensitive = true`
- [ ] S3 buckets block public access unless explicitly needed for static hosting
- [ ] CloudFront uses HTTPS only (or redirect HTTP to HTTPS)
- [ ] ACM certificates use DNS validation
- [ ] Lambda functions use IAM roles with least privilege
- [ ] S3 encryption enabled for non-public buckets
- [ ] CloudFront uses Origin Access Identity (OAI) for S3

## Configuration Strategies

### Locals vs Tfvars: Choosing the Right Approach

#### Recommended: Locals-First for Static Sites

For static sites and serverless apps, use locals for environment-specific configuration:

```hcl
# locals.tf
locals {
  environments = {
    dev = {
      domain_name      = "dev.example.com"
      lambda_memory    = 512
      cloudfront_price_class = "PriceClass_100"
    }
    prod = {
      domain_name      = "www.example.com"
      lambda_memory    = 1024
      cloudfront_price_class = "PriceClass_All"
    }
  }

  env = local.environments[var.environment]

  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }
}

# main.tf
resource "aws_lambda_function" "api" {
  memory_size = local.env.lambda_memory

  tags = local.common_tags
}
```

**Use tfvars for**: Project name, AWS region, shared configuration

## Provider Configuration

### Version Constraints

Always pin provider versions:

```hcl
# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# provider.tf
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

# ACM certificates must be in us-east-1 for CloudFront
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"

  default_tags {
    tags = local.common_tags
  }
}
```

## Resource Patterns

### S3 Static Website Hosting

```hcl
# S3 bucket for static website
resource "aws_s3_bucket" "website" {
  bucket = "${var.project_name}-${var.environment}-site"

  tags = merge(
    local.common_tags,
    {
      Name = "${var.project_name}-${var.environment}-website"
    }
  )
}

# Block public access for CloudFront OAI pattern
resource "aws_s3_bucket_public_access_block" "website" {
  bucket = aws_s3_bucket.website.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Versioning for rollback capability
resource "aws_s3_bucket_versioning" "website" {
  bucket = aws_s3_bucket.website.id

  versioning_configuration {
    status = "Enabled"
  }
}

# Server-side encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# Lifecycle rules for old versions
resource "aws_s3_bucket_lifecycle_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  rule {
    id     = "cleanup-old-versions"
    status = "Enabled"

    noncurrent_version_expiration {
      noncurrent_days = 90
    }
  }
}
```

### CloudFront Distribution

```hcl
# CloudFront Origin Access Identity for S3
resource "aws_cloudfront_origin_access_identity" "website" {
  comment = "OAI for ${var.project_name}-${var.environment}"
}

# S3 bucket policy to allow CloudFront OAI
resource "aws_s3_bucket_policy" "website" {
  bucket = aws_s3_bucket.website.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowCloudFrontOAI"
        Effect = "Allow"
        Principal = {
          AWS = aws_cloudfront_origin_access_identity.website.iam_arn
        }
        Action   = "s3:GetObject"
        Resource = "${aws_s3_bucket.website.arn}/*"
      }
    ]
  })
}

# CloudFront distribution
resource "aws_cloudfront_distribution" "website" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  aliases             = [local.env.domain_name]
  price_class         = local.env.cloudfront_price_class

  origin {
    domain_name = aws_s3_bucket.website.bucket_regional_domain_name
    origin_id   = "S3-${aws_s3_bucket.website.id}"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.website.cloudfront_access_identity_path
    }
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.website.id}"

    forwarded_values {
      query_string = false
      

Related in Cloud & DevOps