liveBook · Manning

Terraform plan

As explained in Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu

The plan phase is the stage of an infrastructure-as-code workflow that computes a proposed set of resource changes.

Suppose you add an AWS instance to a Terraform configuration. Before creating it, run `terraform plan` to see the proposed change. The command calculates a plan; it does not perform the planned resource actions. Review the result, then apply it if it matches your intent. A plan can also be saved to a file for a later step. section 1.4.3 section 2.1.6

Planning has three subphases:

  • Refresh: read infrastructure from the provider and read data sources to detect changes since the previous run. section 1.4.3
  • Compare: check the refreshed state against the resources and configuration described in code. section 1.4.3
  • Plan: construct a directed acyclic graph (DAG) of actions needed to align them. section 1.4.3
Listing 2.5 Terraform planning with the CLI
data.aws_vpc.default: Reading...
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-0cb81cb394fc2e305]
data.aws_vpc.default: Read complete after 0s [id=vpc-cc5449a4]
data.aws_subnets.default: Reading...
data.aws_subnets.default: Read complete after 0s [id=us-east-2]
 
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
 
Terraform will perform the following actions:
 
  # aws_instance.hello_world will be created
  + resource "aws_instance" "hello_world" {
      + ami                                  = "ami-0cb81cb394fc2e305"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = "subnet-b96b6ed1"
      + tags                                 = {
          + "CreatedBy" = "terraform"
        }
      + tags_all                             = {
          + "CreatedBy" = "terraform"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
 
      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)
 
          + capacity_reservation_target {
              + capacity_reservation_id                 = (
known after apply)
              + capacity_reservation_resource_group_arn = (
known after apply)
            }
        }
 
      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
 
      + enclave_options {
          + enabled = (known after apply)
        }
 
      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }
 
      + maintenance_options {
          + auto_recovery = (known after apply)
        }
 
      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (known after apply)
        }
 
      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_card_index    = (known after apply)
          + network_interface_id  = (known after apply)
        }
 
      + private_dns_name_options {
          + enable_resource_name_dns_a_record    = (known after apply)
          + enable_resource_name_dns_aaaa_record = (known after apply)
          + hostname_type                        = (known after apply)
        }
 
      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }
 
Plan: 1 to add, 0 to change, 0 to destroy.
 
Changes to Outputs:
  + aws_instance_arn = (known after apply)
 
────────────────────────────────────────────────────────────────────────────
 
Saved the plan to: plan.tfplan
 
To perform exactly these actions, run the following command to apply:
    terraform apply "plan.tfplan"

The CLI output shows AWS data sources being read before a `+ create` action for one `aws_instance`. Review both the proposed action and the resource it affects before applying. section 2.1.6

The graph determines action order from dependencies, not from the order of blocks in a file. For example, a subnet lookup that uses a VPC lookup’s ID must wait for the VPC lookup. Terraform can infer that relationship from the attribute reference. Because a DAG cannot contain circular dependencies, dependency problems can prevent a valid plan. section 2.2.5 section 1.7

Review a replacement before making it

If a resource needs rebuilding, run `terraform plan -replace 'RESOURCE_ADDRESS'` and inspect the whole result. Replacing one TLS private key in the example also destroys and recreates two dependent resources. Quote an indexed address so the shell does not misinterpret it. Prefer this reviewable plan to the deprecated `terraform taint` command, which changes state immediately. section 5.3.2

A plan cannot supply values that become known only after apply. In a Terraform test run with `command = plan`, an assertion against such an output fails with an unknown-condition error; test values available during planning instead, or use an apply run. section 9.4.1

For automation that needs structured plan data rather than CLI output alone, save a plan file and read it with `terraform show`. That representation includes changes, the prior state, and planned values; producing and processing it requires more than one command. section 11.1.8

How Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu covers it

How deep it goes
Explains refresh, compare, and graph construction, then examines dependency ordering, replacement effects, and structured plan data.
Examples
Uses an AWS instance create plan, a VPC-dependent subnet lookup, TLS key replacement, and a plan-mode test failure.
What it assumes
Assumes a configured workspace and provider access for the AWS walkthrough; explains planning phases and dependency ordering.
What you can do after
Operate/configure it with `terraform plan`, review proposed actions, save a plan, and inspect replacement consequences before apply.
How it connects
Links declarative configuration and state to directed acyclic graphs, data sources, resource dependencies, and apply.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu. The section links point to the parts of the book it draws on.