Create a Terraform plan
The core Terraform workflow consists of three main steps once you have written your Terraform configuration:
- Initialize prepares the working directory so Terraform can run the configuration.
- Plan lets you preview any changes before you apply them.
- Apply executes the changes defined by your Terraform configuration to create, update, or destroy resources.
In order to determine which changes it will make to your infrastructure,
Terraform generates an execution plan. To do so, Terraform reconciles your
Terraform configuration with the real-world infrastructure tracked in that
workspace's state file, and creates a list of resources to create, modify, or
destroy. The plan
command supports multiple flags that let you modify its
behavior which allows you to be flexible in your operations. For example, you can target
specific resources rather than your entire configuration, or run refresh-only
plans that reconcile your state file with the actual configuration of the
resources it tracks.
In this tutorial, you will review how Terraform generates an execution plan,
what it contains, and the function of the plan
command in a Terraform
workflow. To do so, you will create and apply a saved Terraform plan, review
its contents, and analyze how a plan reflects changes to your configuration.
The saved plan file supports Terraform automation workflows in CI/CD pipelines
by guaranteeing that the infrastructure changes Terraform applies match the
ones you or your team approve, even if the deploy process completes across
different machines or different times.
Prerequisites
The tutorial assumes that you are familiar with Terraform. If you are new to Terraform itself, refer first to the Get Started tutorials. For this tutorial, you will need:
- the Terraform 0.14+ CLI installed locally.
- Docker.
- jq.
Clone the example repository
In your terminal, clone the learn-terraform-plan
repository.
Navigate to the cloned repository.
Review configuration
The example configuration in this repository creates two Nginx containers through resources and local and public modules. The nginx
subdirectory contains the local module used to create one of the containers.
Terraform uses the provider versions specified in the terraform.tf
file.
Open the top-level main.tf
file. This configuration uses the docker
provider to download the latest Nginx image and launch a container using that image.
It also references the random_pet
resource to generate a pet name. The module.nginx-pet
block uses the local nginx
module and references the random pet name to create an Nginx container with the pet name.
It also passes the random pet name to the hello
module, which will generate outputs with the random pet name.
Initialize your configuration
In order to generate your execution plan, Terraform needs to install the providers and modules referenced by your configuration. Then, it will reference the locally cached providers and modules to create a plan of resource changes.
Initialize the Terraform configuration to install the required providers and modules.
Create a plan
There are three commands that tell Terraform to generate an execution plan:
The
terraform plan
command lets you to preview the actions Terraform would take to modify your infrastructure, or save a speculative plan which you can apply later. The function ofterraform plan
is speculative: you cannot apply it unless you save its contents and pass them to aterraform apply
command. In an automated Terraform pipeline, applying a saved plan file ensures the changes are the ones expected and scoped by the execution plan, even if your pipeline runs across multiple machines.The
terraform apply
command is the more common workflow outside of automation. If you do not pass a saved plan to the apply command, then it will perform all of the functions of plan and prompt you for approval before making the changes.The
terraform destroy
command creates an execution plan to delete all of the resources managed in that project.
Generate a saved plan with the -out
flag. You will review and apply this plan later in this tutorial.
You can apply the saved plan file to execute these changes, but the contents are not in a human-readable format. Use the terraform show -json
command to convert the plan contents into JSON, then pass it to jq
to format it and save the output into a new file.
Warning
Terraform plan files can contain sensitive data. Never commit a plan file to version control, whether as a binary or in JSON format.
Review a plan
In order to determine the planned changes for your run, Terraform reconciles the prior state and the current configuration. In this section, you will review the data Terraform captures about your resources in a plan file.
At the top of the file, Terraform records the format version and Terraform version used to generate the plan. This will ensure that you use the same version to apply these changes if you used the saved plan.
Review plan configuration
The .configuration
object is a snapshot of your configuration at the time of the terraform plan
.
This configuration snapshot captures the versions of the providers recorded in your .terraform.lock.hcl
file, ensuring that you use the same provider versions that generated the plan to apply the proposed changes. Note that the configuration accounts for both the provider version used by the root module and child modules.
The configuration
section further organizes your resources defined in your top level root_module
.
The module_calls
section contains the details of the modules used, their input variables and outputs, and the resources to create.
The configuration
object also records any references to other resources in a resource's written configuration, which helps Terraform determine the correct order of operations.
Review planned resource changes
Review the planned resources changes to the docker_image.nginx
resource.
The representation includes:
- The
action
field captures the action taken for this resource, in this casecreate
. - The
before
field captures the resource state prior to the run. In this case, the value isnull
because the resource does not yet exist. - The
after
field captures the state to define for the resource. - The
after_unknown
field captures the list of values that will be computed or determined through the operation and sets them totrue
. - The
before_sensitive
andafter_sensitive
fields capture a list of any values markedsensitive
. Terraform will use these lists to determine which output values to redact when you apply your configuration.
Review planned values
The planned_values
object is another view of the differences between the "before" and "after" values of your resources, showing you the planned outcome for a run that would use this plan file.
In this example, the docker_image.nginx
resource includes the address that you will use to reference the resource in your Terraform configuration, the provider name, and the values of all of the attributes as one object. This format resolves the differences between the prior and expected state in one object to demonstrate the planned outcomes for the configuration, which is easier to use for any downstream consumers of the plan data. For example, the Terraform Sentinel CLI tests policies against the planned outcomes recorded here. The cost estimation feature in Terraform Cloud also relies on the planned_values
data to determine changes to your infrastructure spend.
The planned_values
object also lists the resources created by child modules in a separate list, and includes the address of the module.
Apply a saved plan
In your terminal, apply your saved plan.
Note
When you apply a saved plan file, Terraform will not prompt you for approval and instead immediately execute the changes, since this workflow is primarily used in automation.
Confirm that Terraform created the two containers.
Modify configuration
Input variables let you easily update configuration values without having to manually edit your configuration files.
Create a new variables.tf
file in the top-level configuration directory. Add the configuration below to define a new input variable to use for the hello
module.
Then, create a terraform.tfvars
file, and set the new secret_key
input variable value.
Warning
Never commit .tfvars
files to version control.
Finally, update the hello
module configuration in main.tf
to reference the new input variable.
Create a new plan
Create a new Terraform plan and save it as tfplan-input-vars
.
Convert the new plan file into a machine-readable JSON format.
Review new plan
When you created this plan, Terraform determined that the working directory already contains a state file, and used that state to plan the resource changes.
Since Terraform created this plan with existing resources and using input variables, your plan file has some new fields.
Review plan input variables
Now that you have defined input variables, Terraform captures them in the plan file as well.
Warning
Although you marked the input variable as sensitive
, Terraform still stores the value in plaintext in the plan file. Since Terraform plan files can contain sensitive information, you should keep them secure and never commit them to version control.
Unlike input variables, Terraform does not record the values of any environment variables used for your configuration in your plan files. Using environment variables is one of the recommended ways to pass sensitive values, such as provider credentials, to Terraform.
Review plan prior_state
When you created this plan, Terraform determined that the working directory already contains a state file, and used that state to plan the resource changes. Unlike the first run's plan file, this file now contains a prior_state
object, which captures the state file exactly as it was prior to the plan action.
Review resource drift
Terraform also accounts for the possibility that resources have changed outside of the Terraform workflow. As a result, the prior state may not reflect the actual attributes and settings of the resource at the time of the plan
operation, which is known as state "drift". Terraform must reconcile these differences to understand which actions it must actually take to make your resources match the written configuration.
To determine whether state drift occurred, Terraform performs a refresh
operation before it begins to build an execution plan. This refresh step pulls the actual state of all of the resources currently tracked in your state file. Terraform does not update your actual state file, but captures the refreshed state in the plan file.
In this case, Terraform noticed that the provider updated some attributes of the Docker containers and recorded the detected drift.
Notice that the action
listed is an update
, which instructs
Terraform to modify the state to reflect the detected state
drift. Terraform uses this information to provide you with more
detailed plan output and to accurately determine the necessary
changes to your resources. However, Terraform will only apply
changes to your state file during an apply
step.
Review plan resource changes
Now that your state file tracks resources, Terraform will take the existing state into consideration when it creates an execution plan. For example, the module.hello.random_pet.server
object now contains data in both the before
and after
fields, representing the prior and desired configurations respectively.
Notice that the actions
list is now set to ["delete","create"]
and that the action_reason
is "replace_because_cannot_update"
- the change to the secret_key
for the resource is destructive, so Terraform must both delete and create this resource. Terraform determines whether it can update a resource in place or must recreate it based on which provider attributes you changed.
Once you have created resources in the working directory, Terraform uses the prior state, the data returned by a refresh operation, and the written configuration to determine the changes to make. Terraform supports additional flags that you can use to modify how it constructs an execution plan. For example, you can create a plan that only refreshes the state file without modifying resource configuration, or target only specific resources for either update or replacement.
Clean up infrastructure
Now that you have completed this tutorial, destroy the resources created before moving on. Confirm the operation with a yes
.
Next steps
In this tutorial, you reviewed how Terraform constructs an execution plan and uses saved plans. You also explored the relationship of the terraform plan
and terraform apply
commands.
Check out the following tutorials to try out some of the available plan modes and options:
- Learn how to use a refresh-only plan to update your state file with the actual configuration of your resources.
- Learn how to use the
-replace
flag to recreate resources. - Learn more about using resource targeting to scope the resources affected by Terraform operations.
- Review the JSON output format documentation for even more detail on the contents and format of a plan file.