Skip to content

Terraform Patterns

Overview

Terraform handles infrastructure provisioning for AWS and GCE instances.

Workstation Credentials (luna)

Before running Terraform, ensure cloud credentials are loaded.

AWS

AWS CLI uses credential_process for automatic 1Password integration:

# Credentials load automatically via ~/.aws/config
terraform plan

See Secrets Management for details.

GCP

GCP requires sourcing credentials before running Terraform:

# Load credentials (Touch ID prompt)
source scripts/gcp/gcp-env.sh

# Or use alias
gcp-auth

# Then run Terraform
cd cloud/terraform/environments/production/gce/dumbo
terraform plan

See Secrets Management for details.

Critical Constraint: Static IP Protection

NEVER release reserved static IP addresses

These are irreplaceable resources. All static IPs use lifecycle { prevent_destroy = true }.

Protection Strategy

  1. Separate State Files - Static IPs managed in dedicated state, never with instances
  2. prevent_destroy Lifecycle - All static IP resources use prevent_destroy = true
  3. Data Sources for References - Instances reference IPs via data sources, not resource dependencies
  4. No IP Resources in Instance Modules - Instance modules NEVER create/manage IP allocations

Example: AWS Static IP

# static-ips/aws/main.tf - SEPARATE STATE FILE
resource "aws_eip" "pluto" {
  lifecycle {
    prevent_destroy = true  # CRITICAL: Cannot be destroyed
  }
  tags = {
    Name       = "pluto-public-ip"
    managed_by = "terraform"
    critical   = "true"
  }
}

# instances/aws/pluto/main.tf - References IP, doesn't manage it
data "aws_eip" "pluto" {
  filter {
    name   = "tag:Name"
    values = ["pluto-public-ip"]
  }
}

resource "aws_eip_association" "pluto" {
  instance_id   = aws_instance.pluto.id
  allocation_id = data.aws_eip.pluto.id
}

Reserved Static IPs

DO NOT RELEASE THESE IPs

IP Cloud Resource ID Instance Allocated
52.32.80.62 AWS eipalloc-05fa588c23ff2037e pluto 2023-02-28
35.85.90.224 AWS eipalloc-02c6ee27c4a74bdb9 (redshift) ?
34.44.33.3 GCE threefour (us-central1) dumbo ?
35.209.219.216 GCE ? bogart ?
193.8.172.100 Meanservers N/A rocky ?

Directory Structure

terraform/
├── modules/
│   ├── aws-instance/        # Reusable AWS EC2 module
│   ├── gce-instance/        # Reusable GCE VM module
│   └── static-ips/          # Static IP management
└── environments/
    ├── production/
    │   ├── aws/
    │   │   ├── static-ips/  # Static IP state (PROTECTED)
    │   │   ├── pluto/
    │   │   └── mickey/
    │   └── gce/
    │       ├── static-ips/  # Static IP state (PROTECTED)
    │       ├── dumbo/
    │       └── bogart/
    └── test/

AMI/Image Selection

AWS - Ubuntu

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-*-24.04-amd64-server-*"]
  }
}

GCE - Ubuntu

data "google_compute_image" "ubuntu" {
  family  = "ubuntu-2404-lts-amd64"
  project = "ubuntu-os-cloud"
}

Commands

Planning Changes

# Always use -target for instance changes
terraform plan -target=aws_instance.pluto
terraform apply -target=aws_instance.pluto

# NEVER run without -target on instance directories
# (could accidentally affect IP associations)

State Management

# List resources in state
terraform state list

# Show specific resource
terraform state show aws_instance.pluto

# Import existing resource
terraform import aws_instance.pluto i-1234567890abcdef0

Best Practices

AMI Creation

Always stop the instance before creating an AMI:

# Stop instance
aws ec2 stop-instances --instance-ids i-xxx

# Wait for stop
aws ec2 wait instance-stopped --instance-ids i-xxx

# Create AMI
aws ec2 create-image --instance-id i-xxx --name "pluto-backup-$(date +%Y%m%d)"

Consistency

Running instances can have inconsistent filesystem state captured. Use --no-reboot only for quick snapshots where consistency isn't critical.

Instance Recreation

To safely recreate an instance:

  1. Verify static IP is in separate state file
  2. Backup any local data
  3. Run terraform destroy -target=aws_instance.pluto
  4. Run terraform apply -target=aws_instance.pluto
  5. Static IP association will be recreated automatically

Implementation Status

Phase 1: AWS Foundation

  • Terraformer import of existing resources
  • Create aws-instance module
  • Create pluto production config
  • Create static-ips module with prevent_destroy
  • Validate with pluto-dev (complete)
  • Validate pluto can be recreated from IaC

Phase 3: GCE Support (Complete)

  • Create gce-instance module
  • Import dumbo into Terraform state
  • Enable deletion protection on dumbo
  • Validate with dumbo-dev (complete)
  • Import bogart (coop-389306)
  • Validate GCE instances can be recreated