AskLearn
Loading...
← Back to Terraform Course
AdvancedConfiguration

Dependencies and Lifecycle

Resource dependencies

Tutorial 20: Resource Meta-Arguments (depends_on, lifecycle)

Learning Objectives

By the end of this tutorial, you will be able to:

  • Use the depends_on meta-argument to create explicit dependencies
  • Implement lifecycle rules to control resource behavior
  • Handle resource replacement and deletion scenarios
  • Optimize Terraform operations with proper dependency management
  • Apply advanced lifecycle patterns for complex infrastructures

Prerequisites

  • Understanding of Terraform resource dependencies
  • Completed Tutorial 19: Resource Meta-Arguments (count, for_each)
  • Knowledge of Terraform state management

Introduction

The depends_on and lifecycle meta-arguments provide fine-grained control over resource creation, updates, and deletion. These meta-arguments help manage complex dependency chains and protect critical resources from unintended changes.

The depends_on Meta-Argument

Explicit Dependencies

Terraform automatically detects most dependencies through resource references, but sometimes you need to create explicit dependencies that aren't apparent from resource attributes.

# variables.tf
variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "enable_flow_logs" {
  description = "Enable VPC flow logs"
  type        = bool
  default     = true
}

# main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = {
    Name = "main-vpc"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = {
    Name = "main-igw"
  }
}

# This route table depends on both VPC and IGW existing
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  
  tags = {
    Name = "public-route-table"
  }
  
  # Explicit dependency ensures IGW is fully attached before creating routes
  depends_on = [aws_internet_gateway.main]
}

# IAM role for VPC Flow Logs
resource "aws_iam_role" "flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  
  name = "vpc-flow-logs-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "vpc-flow-logs.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  
  name = "vpc-flow-logs-policy"
  role = aws_iam_role.flow_logs[0].id
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
          "logs:DescribeLogGroups",
          "logs:DescribeLogStreams"
        ]
        Effect   = "Allow"
        Resource = "*"
      }
    ]
  })
}

# CloudWatch Log Group for VPC Flow Logs
resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  
  name              = "/aws/vpc/flowlogs"
  retention_in_days = 7
}

# VPC Flow Logs - explicit dependency on IAM role being fully configured
resource "aws_flow_log" "vpc" {
  count = var.enable_flow_logs ? 1 : 0
  
  iam_role_arn    = aws_iam_role.flow_logs[0].arn
  log_destination = aws_cloudwatch_log_group.vpc_flow_logs[0].arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.main.id
  
  # Ensure IAM role and policy are fully created before flow logs
  depends_on = [
    aws_iam_role_policy.flow_logs
  ]
}

Cross-Module Dependencies

# modules/database/main.tf
resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = var.private_subnet_ids
  
  tags = {
    Name = "${var.project_name}-db-subnet-group"
  }
}

resource "aws_db_instance" "main" {
  identifier     = "${var.project_name}-database"
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.micro"
  
  allocated_storage = 20
  storage_encrypted = true
  
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password
  
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [var.db_security_group_id]
  
  skip_final_snapshot = var.environment != "prod"
  
  tags = {
    Name        = "${var.project_name}-database"
    Environment = var.environment
  }
}

# modules/application/main.tf
resource "aws_launch_template" "app" {
  name_prefix   = "${var.project_name}-app-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  vpc_security_group_ids = [var.app_security_group_id]
  
  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    db_endpoint = var.db_endpoint
    db_name     = var.db_name
  }))
  
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name = "${var.project_name}-app-instance"
    }
  }
}

resource "aws_autoscaling_group" "app" {
  name                = "${var.project_name}-app-asg"
  vpc_zone_identifier = var.private_subnet_ids
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"
  
  min_size         = var.min_instances
  max_size         = var.max_instances
  desired_capacity = var.desired_instances
  
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
  
  # Ensure database is ready before launching application instances
  depends_on = [var.database_ready]
  
  tag {
    key                 = "Name"
    value               = "${var.project_name}-app-asg"
    propagate_at_launch = false
  }
}

# Root main.tf
module "database" {
  source = "./modules/database"
  
  project_name         = var.project_name
  environment         = var.environment
  private_subnet_ids  = module.network.private_subnet_ids
  db_security_group_id = module.security.db_security_group_id
  db_name             = var.db_name
  db_username         = var.db_username
  db_password         = var.db_password
}

module "application" {
  source = "./modules/application"
  
  project_name          = var.project_name
  private_subnet_ids    = module.network.private_subnet_ids
  app_security_group_id = module.security.app_security_group_id
  
  db_endpoint = module.database.db_endpoint
  db_name     = var.db_name
  
  # Pass database instance as dependency
  database_ready = module.database.db_instance
  
  # Alternative: explicit dependency
  depends_on = [module.database]
}

Complex Dependency Scenarios

# DNS and Certificate Management with Dependencies
resource "aws_acm_certificate" "app" {
  domain_name               = var.domain_name
  subject_alternative_names = ["*.${var.domain_name}"]
  validation_method         = "DNS"
  
  lifecycle {
    create_before_destroy = true
  }
}

# DNS validation records
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.app.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
  
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.main.zone_id
}

# Certificate validation
resource "aws_acm_certificate_validation" "app" {
  certificate_arn         = aws_acm_certificate.app.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
  
  timeouts {
    create = "5m"
  }
}

# Load Balancer - depends on certificate being validated
resource "aws_lb" "app" {
  name               = "${var.project_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets           = var.public_subnet_ids
  
  enable_deletion_protection = var.environment == "prod"
  
  tags = {
    Name = "${var.project_name}-alb"
  }
}

# HTTPS Listener - explicit dependency on certificate validation
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.app.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = aws_acm_certificate.app.arn
  
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
  
  # Ensure certificate is validated before creating HTTPS listener
  depends_on = [aws_acm_certificate_validation.app]
}

# DNS record pointing to load balancer
resource "aws_route53_record" "app" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = var.domain_name
  type    = "A"
  
  alias {
    name                   = aws_lb.app.dns_name
    zone_id                = aws_lb.app.zone_id
    evaluate_target_health = true
  }
  
  # Ensure load balancer is ready before creating DNS record
  depends_on = [aws_lb.app]
}

data "aws_route53_zone" "main" {
  name         = var.domain_name
  private_zone = false
}

The lifecycle Meta-Argument

Create Before Destroy

Useful for resources that can't have downtime during replacement.

# Launch Template with create_before_destroy
resource "aws_launch_template" "app" {
  name_prefix   = "${var.project_name}-app-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    app_version = var.app_version
  }))
  
  # Create new launch template before destroying old one
  lifecycle {
    create_before_destroy = true
  }
  
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name    = "${var.project_name}-app"
      Version = var.app_version
    }
  }
}

# Security Group with create_before_destroy
resource "aws_security_group" "app" {
  name_prefix = "${var.project_name}-app-"
  vpc_id      = var.vpc_id
  description = "Security group for application instances"
  
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr]
  }
  
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr]
  }
  
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # Ensure new security group is created before destroying old one
  lifecycle {
    create_before_destroy = true
  }
  
  tags = {
    Name = "${var.project_name}-app-security-group"
  }
}

# Auto Scaling Group using the launch template
resource "aws_autoscaling_group" "app" {
  name                = "${var.project_name}-app-asg"
  vpc_zone_identifier = var.private_subnet_ids
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"
  
  min_size         = var.min_instances
  max_size         = var.max_instances
  desired_capacity = var.desired_instances
  
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
  
  # Rolling update configuration
  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }
  
  tag {
    key                 = "Name"
    value               = "${var.project_name}-app-instance"
    propagate_at_launch = true
  }
}

Prevent Destroy

Protect critical resources from accidental deletion.

# Critical database with prevent_destroy
resource "aws_db_instance" "production" {
  identifier     = "${var.project_name}-prod-db"
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.r5.large"
  
  allocated_storage     = 100
  max_allocated_storage = 1000
  storage_encrypted     = true
  kms_key_id           = aws_kms_key.db.arn
  
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password
  
  backup_retention_period = 30
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"
  
  deletion_protection = true
  skip_final_snapshot = false
  final_snapshot_identifier = "${var.project_name}-prod-db-final-snapshot"
  
  # Prevent accidental destruction of production database
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Name        = "${var.project_name}-production-database"
    Environment = "prod"
    Critical    = "true"
  }
}

# S3 bucket for backups with prevent_destroy
resource "aws_s3_bucket" "backups" {
  bucket = "${var.project_name}-backups-${random_id.bucket_suffix.hex}"
  
  # Prevent accidental deletion of backup bucket
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Name     = "${var.project_name}-backups"
    Purpose  = "backups"
    Critical = "true"
  }
}

resource "aws_s3_bucket_versioning" "backups" {
  bucket = aws_s3_bucket.backups.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "backups" {
  bucket = aws_s3_bucket.backups.id
  
  rule {
    id     = "backup_lifecycle"
    status = "Enabled"
    
    noncurrent_version_expiration {
      noncurrent_days = 90
    }
    
    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}

# KMS key for encryption
resource "aws_kms_key" "db" {
  description             = "KMS key for database encryption"
  deletion_window_in_days = 30
  
  # Protect encryption key from deletion
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Name = "${var.project_name}-db-encryption-key"
  }
}

resource "random_id" "bucket_suffix" {
  byte_length = 4
}

Ignore Changes

Ignore changes to specific attributes that might be modified outside Terraform.

# Auto Scaling Group with ignore_changes for desired_capacity
resource "aws_autoscaling_group" "app" {
  name                = "${var.project_name}-app-asg"
  vpc_zone_identifier = var.private_subnet_ids
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"
  
  min_size         = var.min_instances
  max_size         = var.max_instances
  desired_capacity = var.desired_instances
  
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
  
  # Ignore changes to desired_capacity as it may be modified by auto-scaling
  lifecycle {
    ignore_changes = [desired_capacity]
  }
  
  tag {
    key                 = "Name"
    value               = "${var.project_name}-app-asg"
    propagate_at_launch = false
  }
}

# EC2 instance with ignore_changes for tags that might be added by other systems
resource "aws_instance" "app" {
  count = var.instance_count
  
  ami           = var.ami_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_ids[count.index % length(var.subnet_ids)]
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  # Base tags that Terraform manages
  tags = {
    Name        = "${var.project_name}-app-${count.index + 1}"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
  
  # Ignore changes to tags that might be added by monitoring or other systems
  lifecycle {
    ignore_changes = [
      tags["LastBackup"],
      tags["MonitoringAgent"],
      tags["SecurityScan"]
    ]
  }
}

# RDS instance ignoring minor version updates
resource "aws_db_instance" "app" {
  identifier     = "${var.project_name}-app-db"
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = var.db_instance_class
  
  allocated_storage = var.db_storage
  storage_encrypted = true
  
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password
  
  auto_minor_version_upgrade = true
  
  # Ignore minor version updates as they're applied automatically
  lifecycle {
    ignore_changes = [engine_version]
  }
  
  tags = {
    Name = "${var.project_name}-app-database"
  }
}

Replace Triggered By

Force replacement when specific values change.

# EC2 instance that recreates when user data changes
resource "aws_instance" "app" {
  count = var.instance_count
  
  ami           = var.ami_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_ids[count.index % length(var.subnet_ids)]
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  user_data = templatefile("${path.module}/user_data.sh", {
    app_version    = var.app_version
    configuration  = var.app_configuration
    environment    = var.environment
  })
  
  # Force replacement when specific variables change
  lifecycle {
    replace_triggered_by = [
      var.app_version,
      var.force_replacement_trigger
    ]
  }
  
  tags = {
    Name       = "${var.project_name}-app-${count.index + 1}"
    Version    = var.app_version
    DeployedAt = timestamp()
  }
}

# Variable to trigger replacement
variable "force_replacement_trigger" {
  description = "Change this value to force instance replacement"
  type        = string
  default     = "1"
}

# Lambda function that recreates when code changes
resource "aws_lambda_function" "api" {
  filename         = "api.zip"
  function_name    = "${var.project_name}-api"
  role            = aws_iam_role.lambda.arn
  handler         = "index.handler"
  runtime         = "nodejs18.x"
  source_code_hash = data.archive_file.api.output_base64sha256
  
  environment {
    variables = var.lambda_environment_variables
  }
  
  # Force replacement when environment variables change significantly
  lifecycle {
    replace_triggered_by = [
      var.lambda_force_redeploy
    ]
  }
  
  tags = {
    Name    = "${var.project_name}-api"
    Version = var.api_version
  }
}

data "archive_file" "api" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "api.zip"
}

Advanced Lifecycle Patterns

Blue-Green Deployment Pattern

# variables.tf
variable "deployment_color" {
  description = "Current deployment color (blue or green)"
  type        = string
  default     = "blue"
  
  validation {
    condition     = contains(["blue", "green"], var.deployment_color)
    error_message = "Deployment color must be either 'blue' or 'green'."
  }
}

variable "previous_color" {
  description = "Previous deployment color"
  type        = string
  default     = "green"
}

# main.tf
locals {
  deployment_name = "${var.project_name}-${var.deployment_color}"
  is_blue_active  = var.deployment_color == "blue"
}

# Launch template for current deployment
resource "aws_launch_template" "current" {
  name_prefix   = "${local.deployment_name}-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    deployment_color = var.deployment_color
    app_version     = var.app_version
  }))
  
  lifecycle {
    create_before_destroy = true
  }
  
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name           = "${local.deployment_name}-instance"
      DeploymentColor = var.deployment_color
      Version        = var.app_version
    }
  }
}

# Auto Scaling Group for current deployment
resource "aws_autoscaling_group" "current" {
  name                = "${local.deployment_name}-asg"
  vpc_zone_identifier = var.private_subnet_ids
  health_check_type   = "ELB"
  
  min_size         = var.min_instances
  max_size         = var.max_instances
  desired_capacity = var.desired_instances
  
  # Only attach to load balancer target group when this is the active deployment
  target_group_arns = local.is_blue_active ? [aws_lb_target_group.blue.arn] : [aws_lb_target_group.green.arn]
  
  launch_template {
    id      = aws_launch_template.current.id
    version = "$Latest"
  }
  
  lifecycle {
    create_before_destroy = true
  }
  
  tag {
    key                 = "Name"
    value               = "${local.deployment_name}-asg"
    propagate_at_launch = false
  }
  
  tag {
    key                 = "DeploymentColor"
    value               = var.deployment_color
    propagate_at_launch = true
  }
}

# Target groups for blue-green deployment
resource "aws_lb_target_group" "blue" {
  name     = "${var.project_name}-blue-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
  
  health_check {
    enabled             = true
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 5
    interval            = 30
    path                = "/health"
    matcher             = "200"
  }
  
  tags = {
    Name            = "${var.project_name}-blue-target-group"
    DeploymentColor = "blue"
  }
}

resource "aws_lb_target_group" "green" {
  name     = "${var.project_name}-green-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
  
  health_check {
    enabled             = true
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 5
    interval            = 30
    path                = "/health"
    matcher             = "200"
  }
  
  tags = {
    Name            = "${var.project_name}-green-target-group"
    DeploymentColor = "green"
  }
}

# Load balancer listener that routes to active deployment
resource "aws_lb_listener" "app" {
  load_balancer_arn = aws_lb.app.arn
  port              = "80"
  protocol          = "HTTP"
  
  default_action {
    type             = "forward"
    target_group_arn = local.is_blue_active ? aws_lb_target_group.blue.arn : aws_lb_target_group.green.arn
  }
}

Database Migration Pattern

# Database with migration-friendly lifecycle
resource "aws_db_instance" "app" {
  identifier     = "${var.project_name}-db-${var.db_version}"
  engine         = "mysql"
  engine_version = var.db_engine_version
  instance_class = var.db_instance_class
  
  allocated_storage     = var.db_storage
  max_allocated_storage = var.db_max_storage
  storage_encrypted     = true
  
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password
  
  backup_retention_period = 7
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"
  
  # Create snapshot before any major changes
  skip_final_snapshot       = false
  final_snapshot_identifier = "${var.project_name}-db-${var.db_version}-final"
  
  # Allow engine version updates but create before destroy for major versions
  lifecycle {
    create_before_destroy = var.db_major_version_upgrade
    ignore_changes = var.db_ignore_minor_versions ? [engine_version] : []
  }
  
  tags = {
    Name     = "${var.project_name}-database"
    Version  = var.db_version
    Snapshot = "enabled"
  }
}

# Database parameter group with versioning
resource "aws_db_parameter_group" "app" {
  family = "${var.db_engine}${var.db_major_version}"
  name   = "${var.project_name}-db-params-${var.db_version}"
  
  dynamic "parameter" {
    for_each = var.db_parameters
    content {
      name  = parameter.value.name
      value = parameter.value.value
    }
  }
  
  lifecycle {
    create_before_destroy = true
  }
  
  tags = {
    Name    = "${var.project_name}-db-parameters"
    Version = var.db_version
  }
}

# Read replica with conditional creation
resource "aws_db_instance" "read_replica" {
  count = var.create_read_replica ? 1 : 0
  
  identifier             = "${var.project_name}-db-replica"
  replicate_source_db    = aws_db_instance.app.identifier
  instance_class         = var.replica_instance_class
  publicly_accessible    = false
  auto_minor_version_upgrade = false
  
  # Read replica lifecycle management
  lifecycle {
    ignore_changes = [
      replicate_source_db,  # Don't change source after creation
    ]
  }
  
  tags = {
    Name = "${var.project_name}-database-replica"
    Type = "read-replica"
  }
}

Best Practices

1. Dependency Management

# Good: Clear dependency chain
resource "aws_iam_role" "lambda" {
  name = "${var.project_name}-lambda-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_basic" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  role       = aws_iam_role.lambda.name
}

resource "aws_lambda_function" "api" {
  filename      = "api.zip"
  function_name = "${var.project_name}-api"
  role         = aws_iam_role.lambda.arn
  handler      = "index.handler"
  runtime      = "nodejs18.x"
  
  # Explicit dependency ensures role is fully configured
  depends_on = [
    aws_iam_role_policy_attachment.lambda_basic,
    aws_cloudwatch_log_group.lambda
  ]
}

# Pre-create log group with retention
resource "aws_cloudwatch_log_group" "lambda" {
  name              = "/aws/lambda/${var.project_name}-api"
  retention_in_days = 14
}

2. Lifecycle Rule Application

# Strategic use of lifecycle rules
resource "aws_launch_template" "app" {
  name_prefix   = "${var.project_name}-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  # Use create_before_destroy for zero-downtime updates
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_db_instance" "production" {
  identifier = "${var.project_name}-prod-db"
  # ... other configuration
  
  # Protect critical production resources
  lifecycle {
    prevent_destroy = var.environment == "prod"
    ignore_changes  = var.environment == "prod" ? [password] : []
  }
}

resource "aws_autoscaling_group" "app" {
  name = "${var.project_name}-asg"
  # ... other configuration
  
  # Ignore capacity changes made by auto-scaling policies
  lifecycle {
    ignore_changes = [desired_capacity]
  }
}

3. Conditional Lifecycle Rules

# Environment-specific lifecycle rules
resource "aws_instance" "app" {
  count = var.instance_count
  
  ami           = var.ami_id
  instance_type = var.instance_type
  
  tags = {
    Name        = "${var.project_name}-app-${count.index + 1}"
    Environment = var.environment
  }
  
  # Different lifecycle rules based on environment
  dynamic "lifecycle" {
    for_each = var.environment == "prod" ? [1] : []
    content {
      prevent_destroy = true
      ignore_changes  = [ami, user_data]
    }
  }
}

Key Takeaways

  1. Explicit Dependencies: Use depends_on when implicit dependencies aren't sufficient
  2. Resource Protection: Use prevent_destroy for critical production resources
  3. Zero Downtime: Use create_before_destroy for resources that can't have downtime
  4. External Changes: Use ignore_changes for attributes modified outside Terraform
  5. Forced Updates: Use replace_triggered_by to control when resources are recreated
  6. Environment Awareness: Apply different lifecycle rules based on environment

Next Steps

  • Tutorial 21: Learn about data sources and external data integration
  • Practice implementing blue-green deployment patterns
  • Experiment with complex dependency scenarios
  • Review the Terraform Meta-Arguments Documentation

Additional Resources