What is a Terraform Plan?
Terraform is a popular tool for managing infrastructure as code. It allows you to define and provision your resources in a declarative way, using a simple and human-readable language. But before you apply your configuration to create or update your infrastructure, you need to plan it. That's where Terraform Plan comes in and the topic of this blog post.
Video Walk-through
Our setup
Let’s take a look at our setup.
Requirements
- A GitHub account (all the hands-on sections will utilize GitHub’s Codespaces so you won’t need to install anything on your machine)
Repository
TL;DR: You can find the repo here.
The Terraform Plan Command
Before jumping into the code example, let's understand some of the concepts around the Terraform Plan command.
Terraform Plan is a command that shows you what Terraform will do before it actually does it. It compares your current state (the existing resources) with your desired state (the configuration file) and generates a plan of action to achieve it. The plan shows you which resources will be created, modified, or destroyed, and how their attributes will change.
Purpose of Terraform Plan
Terraform Plan aims to help you review and verify your configuration before applying it. It gives you a chance to catch any errors or inconsistencies in your terraform code, and to make sure that you are not making any unwanted changes to your infrastructure. It also helps you communicate and collaborate with your team members, by showing them what you intend to do and getting their feedback.
Why is Terraform Plan important?
Planning your infrastructure changes is very important. Let’s first talk about the challenges that you may encounter when you don’t plan then discuss the importance of running plans.
Challenges without planning
Without planning you may run into the following challenges:
- Run the risk of applying changes that are not intended or expected, which can cause errors, downtime, or security issues.
- May not be aware of the dependencies or conflicts between your resources, which can lead to failures or inconsistencies.
- May not be able to estimate the cost or the impact of your changes, which can affect your budget or your performance.
The Importance of Terraform Plan
Planning helps you:
- Avoid the challenges mentioned above by showing you exactly what Terraform will do and allowing you to review it before executing it.
- Ensure that your configuration is valid and consistent with your desired state and follows the best practices and conventions of Terraform.
- Optimize your resources and reduce waste by showing you what resources are no longer needed and can be removed.
- Improve your collaboration and transparency by sharing your plan with your team members and stakeholders and getting their approval before applying it.
Terraform Plan in Action
To see how Terraform Plan works in action, let's look at a few use case examples based on pulling an NGINX docker image and creating a docker container with this image.
Use Case Examples
Creation of a new resource
The process of creating a new resource is simple and we will take it one step at a time.
- Write configuration files that define the resources and their attributes. Below you will see all the terraform files.
Here is the main.tf file:
Then we have a variables.tf file that defines the following variables:
- image_name
- image_tag
- internal_port
- external_port
We then have a terraform.tfvars file to assign the variable values. There are many other ways to assign these variables such as using environment variables, but here we will define them in a file.
- Run terraform init command to initialize the Terraform folder.
The output will look like this:
- Run the terraform plan command to see the execution plan and what resources will be created and how they will be configured.
Here is the terraform plan output that shows the proposed changes:
- Review the plan and confirm that it matches your expectations.
- Run the terraform apply command to create the resources
When you run terraform apply, a terraform plan is created automatically with a prompt at the end to approve the plan and apply the configuration changes. Below is the output prompt to accept the plan along with the execution of the plan.
You can also view the newly created NGINX container by going to the ports tab and clicking the globe icon as shown below.
And you will get the famous NGINX welcome screen
Modification of an existing resource
Now let's say you want to modify the container's restart behavior from always restarting to restarting on-failure. Just modify the docker_container resource in the main.tf file as shown below.
Now run the terraform command again to see what resource will be modified and how its attribute will change. You review the plan and confirm that it matches your expectations. You run Terraform Apply to update the resource.
Notice how the output below shows that an update in-place will occur. This means that Terraform will not destroy any resources, it will update the resource in place. This behavior is dependent on the type of resource and provider being used along with which attribute we're changing. In our case, changing the restart behavior can be done with no destruction.
To demonstrate a modification that will cause the container to be destroyed and created, let's modify the external port number to use. Change the external port in the terraform.tfvars file from 8080 to 8081.
Now run terraform plan again and observe the output below:
You don't need to apply this change as we're just demonstrating what a destroy and then create replacement looks like. This is a very important change that you would need to pay attention to when running the terraform plan command.
Deletion of an existing resource
Let's say we want to now delete our container but would like to keep our docker image. To do this, we can easily update our main.tf file and comment out our docker_container resource as shown below:
This is telling Terraform that we want to delete this resource. Now go ahead and run the plan again.
Here is the execution plan output showing that we will destroy the NGINX docker container:
We are going to go ahead and run a terraform apply to delete the container, but it's very important to pay attention to resources that are planned for destruction.
You can verify that the docker container is removed by running docker ps to see that there are no containers running.
Planning modes
Terraform Plan has two modes: normal mode and refresh-only mode.
- Normal mode is the default mode that we've been using all along. It compares the current state with the desired state and generates a plan of action to achieve it. It also refreshes the state by querying the providers for any changes that may have occurred outside of Terraform.
Refresh-only mode is a special mode that only refreshes the state without generating a plan of action. It is useful when you want to update the state without making any changes to the infrastructure. It is also a faster operation since we're skipping the normal plan phase which could take some time in large configurations.
Let's try this out. First we need to recreate our infrastructure. Run the following command and accept the prompt by typing ‘yes’
Now go ahead and delete our NGINX docker image using the following command:
Now run the following terraform command:
and the output looks like this:
This is showing you that changes occurred outside of Terraform. That is correct since we deleted the docker image outside of Terraform. Now since this is a terraform plan command, it will not refresh the state file. Take a look at your state file called terraform.tfstate. This is an example of what it would look like right now:
Notice how we still have our docker image. Now let's run the following command to refresh this state file and remove the docker image from it.
answer 'yes' to the prompt and observe the state file now:
We've successfully removed the docker image from our state file. Using the -refresh-only flag with the terraform plan and terraform apply commands is a much safer option than the older method of using the terraform refresh command.
Planning options
Terraform Plan has several options that allow you to customize how it works. In this section, we will discuss the most popular ones, you can get a list of all the options using the terraform plan -h command.
- The -out flag. This flag allows you to save the output of Terraform plan to a file, which you can then use as an input for Terraform apply. This ensures that Terraform apply will execute exactly the same actions as Terraform plan, without any possibility of drift or interference. This is especially useful if you have a long-running or complex plan that you want to review carefully before applying, or if you want to automate your Terraform workflow with scripts or CI/CD tools. Try this:
Notice the output:
And now you run terraform apply while referencing this tfplan file as shown:
- The -target flag. This flag allows you to specify one or more resources that you want Terraform to focus on when planning and applying. This can be useful if you want to test or debug a specific resource without affecting the rest of your infrastructure, or if you want to make a quick change without waiting for a full plan and apply. However, be careful when using this option, as it may cause dependencies or side effects that are not captured by the plan. We will discuss this flag in more detail later under the Resource Targeting section.
- The -replace flag. This flag forces the replacement of a particular resource instance using its resource address. If the plan would've normally produced an update or no-op action for this instance, Terraform will plan to replace it instead. You can use this option multiple times to replace more than one object. In the past, Terraform used the terraform taint command to do the same, but it's now recommended to use the -replace flag with terraform plan and terraform apply. Let's give it a try.
This will effectively replace our docker NGINX container. So it will destroy and create. Here is the summary output of this command:
To go ahead and apply the change, you will need to run:
- The -var and -var-file flags. These flags allow you to pass variables to Terraform plan, either as individual key-value pairs or as files containing multiple variables. Variables are useful for parameterizing your Terraform configuration and making it more reusable and flexible. So far, we've been using the variables.tf and the terraform.tfvars files to declare and assign variable values, respectively. Now comment out everything in the terraform.tfvars file to like this:
Now if you run terraform plan, you will be prompted to supply values for both the image_name and the image_tag since they don't have default values specified in the variables.tf file as shown below:
To eliminate the need to answer these prompts, we can use the -var flag to provide the needed variables via the command line like this:
This should work fine and you should get a No changes to your infrastructure message.We could also use a variables file that perhaps is located in a different directory than our current one. Remember that Terraform looks for all its files in the current directory you're running Terraform from. Now check the folder called a_random_directory and the file called my_variables_file.txt. Notice how we gave this file the .txt extension to show you that we can include non-terraform-specific files to the terraform plan command. You'll see that we simply defined the two needed variables there:
Now let's run terraform plan again:
This should work fine and you should get a No changes to your infrastructure message.
- The -destroy flag. This creates a plan to destroy all objects currently managed by this Terraform configuration. It's similar to running terraform destroy, however, it's useful when used in CI/CD pipelines. Now uncomment the variables in the terraform.tfvars file and run the following execution plan command:
To apply the changes, go ahead and run the following command:
Resource Targeting
Resource targeting in Terraform is a feature that allows you to apply changes to only a subset of your infrastructure, instead of applying the entire plan at once. This can be useful for troubleshooting errors, recovering from network failures, or testing out new configurations without affecting the rest of your resources.
We already mentioned how to target certain resources using the -target flag when you run terraform plan or terraform apply. You can provide one or more target options that contain references to resources, modules, or collections of resources in your configuration. For example:
This command will generate a plan that only includes the aws_instance.web resource and all the resources in the module.network module. Terraform will ignore any other resources or modules that are not explicitly targeted.
Resource targeting can be a powerful tool for managing your infrastructure, but it should not be part of your regular workflow. It can cause inconsistencies in your state file and lead to unexpected dependencies or conflicts between resources. It is recommended to use resource targeting only as a last resort when you need to fix a specific problem or test a specific change. For most cases, you should apply your entire plan as defined by your configuration files.
Let's give it a try with our docker example.
First, let's rebuild our infrastructure:
type 'yes' for the prompt.
Now let's change the image_tag from "1.23.3" to "1.23.1" in the terraform.tfvars file.
Run terraform plan again and notice that both the docker_container.nginx_container and the docker_image.nginx_image resources are planned to be destroyed and recreated.
To target only the docker image to be recreated let's use our -target flag:
Notice now that only the image will be updated. Also, notice the warning message about resource targeting being in effect.
You can go ahead and apply the changes with the following command:
Now notice the error message when we try to apply:
This shows the dependency that this container has on the current image tag. That's why the resource targeting capability needs to be used with care.
Best Practices
To make the most of Terraform Plan and Terraform in general, here are some best practices to follow:
Conclusion
Terraform Plan is a powerful and useful command that lets you preview the changes that Terraform will make to your infrastructure before applying them. It helps you validate your configuration, avoid errors and unwanted changes, and collaborate with your team. In this blog post, we learned about the purpose and importance of Terraform Plan, and how to use it in different scenarios. We also saw how to save and apply a plan file, and how to use some of the options and flags that Terraform Plan supports. By using Terraform Plan, you can make your infrastructure as code more reliable, predictable, and transparent.
What is a Terraform Plan?
Terraform is a popular tool for managing infrastructure as code. It allows you to define and provision your resources in a declarative way, using a simple and human-readable language. But before you apply your configuration to create or update your infrastructure, you need to plan it. That's where Terraform Plan comes in and the topic of this blog post.
Video Walk-through
Our setup
Let’s take a look at our setup.
Requirements
- A GitHub account (all the hands-on sections will utilize GitHub’s Codespaces so you won’t need to install anything on your machine)
Repository
TL;DR: You can find the repo here.
The Terraform Plan Command
Before jumping into the code example, let's understand some of the concepts around the Terraform Plan command.
Terraform Plan is a command that shows you what Terraform will do before it actually does it. It compares your current state (the existing resources) with your desired state (the configuration file) and generates a plan of action to achieve it. The plan shows you which resources will be created, modified, or destroyed, and how their attributes will change.
Purpose of Terraform Plan
Terraform Plan aims to help you review and verify your configuration before applying it. It gives you a chance to catch any errors or inconsistencies in your terraform code, and to make sure that you are not making any unwanted changes to your infrastructure. It also helps you communicate and collaborate with your team members, by showing them what you intend to do and getting their feedback.
Why is Terraform Plan important?
Planning your infrastructure changes is very important. Let’s first talk about the challenges that you may encounter when you don’t plan then discuss the importance of running plans.
Challenges without planning
Without planning you may run into the following challenges:
- Run the risk of applying changes that are not intended or expected, which can cause errors, downtime, or security issues.
- May not be aware of the dependencies or conflicts between your resources, which can lead to failures or inconsistencies.
- May not be able to estimate the cost or the impact of your changes, which can affect your budget or your performance.
The Importance of Terraform Plan
Planning helps you:
- Avoid the challenges mentioned above by showing you exactly what Terraform will do and allowing you to review it before executing it.
- Ensure that your configuration is valid and consistent with your desired state and follows the best practices and conventions of Terraform.
- Optimize your resources and reduce waste by showing you what resources are no longer needed and can be removed.
- Improve your collaboration and transparency by sharing your plan with your team members and stakeholders and getting their approval before applying it.
Terraform Plan in Action
To see how Terraform Plan works in action, let's look at a few use case examples based on pulling an NGINX docker image and creating a docker container with this image.
Use Case Examples
Creation of a new resource
The process of creating a new resource is simple and we will take it one step at a time.
- Write configuration files that define the resources and their attributes. Below you will see all the terraform files.
Here is the main.tf file:
Then we have a variables.tf file that defines the following variables:
- image_name
- image_tag
- internal_port
- external_port
We then have a terraform.tfvars file to assign the variable values. There are many other ways to assign these variables such as using environment variables, but here we will define them in a file.
- Run terraform init command to initialize the Terraform folder.
The output will look like this:
- Run the terraform plan command to see the execution plan and what resources will be created and how they will be configured.
Here is the terraform plan output that shows the proposed changes:
- Review the plan and confirm that it matches your expectations.
- Run the terraform apply command to create the resources
When you run terraform apply, a terraform plan is created automatically with a prompt at the end to approve the plan and apply the configuration changes. Below is the output prompt to accept the plan along with the execution of the plan.
You can also view the newly created NGINX container by going to the ports tab and clicking the globe icon as shown below.
And you will get the famous NGINX welcome screen
Modification of an existing resource
Now let's say you want to modify the container's restart behavior from always restarting to restarting on-failure. Just modify the docker_container resource in the main.tf file as shown below.
Now run the terraform command again to see what resource will be modified and how its attribute will change. You review the plan and confirm that it matches your expectations. You run Terraform Apply to update the resource.
Notice how the output below shows that an update in-place will occur. This means that Terraform will not destroy any resources, it will update the resource in place. This behavior is dependent on the type of resource and provider being used along with which attribute we're changing. In our case, changing the restart behavior can be done with no destruction.
To demonstrate a modification that will cause the container to be destroyed and created, let's modify the external port number to use. Change the external port in the terraform.tfvars file from 8080 to 8081.
Now run terraform plan again and observe the output below:
You don't need to apply this change as we're just demonstrating what a destroy and then create replacement looks like. This is a very important change that you would need to pay attention to when running the terraform plan command.
Deletion of an existing resource
Let's say we want to now delete our container but would like to keep our docker image. To do this, we can easily update our main.tf file and comment out our docker_container resource as shown below:
This is telling Terraform that we want to delete this resource. Now go ahead and run the plan again.
Here is the execution plan output showing that we will destroy the NGINX docker container:
We are going to go ahead and run a terraform apply to delete the container, but it's very important to pay attention to resources that are planned for destruction.
You can verify that the docker container is removed by running docker ps to see that there are no containers running.
Planning modes
Terraform Plan has two modes: normal mode and refresh-only mode.
- Normal mode is the default mode that we've been using all along. It compares the current state with the desired state and generates a plan of action to achieve it. It also refreshes the state by querying the providers for any changes that may have occurred outside of Terraform.
Refresh-only mode is a special mode that only refreshes the state without generating a plan of action. It is useful when you want to update the state without making any changes to the infrastructure. It is also a faster operation since we're skipping the normal plan phase which could take some time in large configurations.
Let's try this out. First we need to recreate our infrastructure. Run the following command and accept the prompt by typing ‘yes’
Now go ahead and delete our NGINX docker image using the following command:
Now run the following terraform command:
and the output looks like this:
This is showing you that changes occurred outside of Terraform. That is correct since we deleted the docker image outside of Terraform. Now since this is a terraform plan command, it will not refresh the state file. Take a look at your state file called terraform.tfstate. This is an example of what it would look like right now:
Notice how we still have our docker image. Now let's run the following command to refresh this state file and remove the docker image from it.
answer 'yes' to the prompt and observe the state file now:
We've successfully removed the docker image from our state file. Using the -refresh-only flag with the terraform plan and terraform apply commands is a much safer option than the older method of using the terraform refresh command.
Planning options
Terraform Plan has several options that allow you to customize how it works. In this section, we will discuss the most popular ones, you can get a list of all the options using the terraform plan -h command.
- The -out flag. This flag allows you to save the output of Terraform plan to a file, which you can then use as an input for Terraform apply. This ensures that Terraform apply will execute exactly the same actions as Terraform plan, without any possibility of drift or interference. This is especially useful if you have a long-running or complex plan that you want to review carefully before applying, or if you want to automate your Terraform workflow with scripts or CI/CD tools. Try this:
Notice the output:
And now you run terraform apply while referencing this tfplan file as shown:
- The -target flag. This flag allows you to specify one or more resources that you want Terraform to focus on when planning and applying. This can be useful if you want to test or debug a specific resource without affecting the rest of your infrastructure, or if you want to make a quick change without waiting for a full plan and apply. However, be careful when using this option, as it may cause dependencies or side effects that are not captured by the plan. We will discuss this flag in more detail later under the Resource Targeting section.
- The -replace flag. This flag forces the replacement of a particular resource instance using its resource address. If the plan would've normally produced an update or no-op action for this instance, Terraform will plan to replace it instead. You can use this option multiple times to replace more than one object. In the past, Terraform used the terraform taint command to do the same, but it's now recommended to use the -replace flag with terraform plan and terraform apply. Let's give it a try.
This will effectively replace our docker NGINX container. So it will destroy and create. Here is the summary output of this command:
To go ahead and apply the change, you will need to run:
- The -var and -var-file flags. These flags allow you to pass variables to Terraform plan, either as individual key-value pairs or as files containing multiple variables. Variables are useful for parameterizing your Terraform configuration and making it more reusable and flexible. So far, we've been using the variables.tf and the terraform.tfvars files to declare and assign variable values, respectively. Now comment out everything in the terraform.tfvars file to like this:
Now if you run terraform plan, you will be prompted to supply values for both the image_name and the image_tag since they don't have default values specified in the variables.tf file as shown below:
To eliminate the need to answer these prompts, we can use the -var flag to provide the needed variables via the command line like this:
This should work fine and you should get a No changes to your infrastructure message.We could also use a variables file that perhaps is located in a different directory than our current one. Remember that Terraform looks for all its files in the current directory you're running Terraform from. Now check the folder called a_random_directory and the file called my_variables_file.txt. Notice how we gave this file the .txt extension to show you that we can include non-terraform-specific files to the terraform plan command. You'll see that we simply defined the two needed variables there:
Now let's run terraform plan again:
This should work fine and you should get a No changes to your infrastructure message.
- The -destroy flag. This creates a plan to destroy all objects currently managed by this Terraform configuration. It's similar to running terraform destroy, however, it's useful when used in CI/CD pipelines. Now uncomment the variables in the terraform.tfvars file and run the following execution plan command:
To apply the changes, go ahead and run the following command:
Resource Targeting
Resource targeting in Terraform is a feature that allows you to apply changes to only a subset of your infrastructure, instead of applying the entire plan at once. This can be useful for troubleshooting errors, recovering from network failures, or testing out new configurations without affecting the rest of your resources.
We already mentioned how to target certain resources using the -target flag when you run terraform plan or terraform apply. You can provide one or more target options that contain references to resources, modules, or collections of resources in your configuration. For example:
This command will generate a plan that only includes the aws_instance.web resource and all the resources in the module.network module. Terraform will ignore any other resources or modules that are not explicitly targeted.
Resource targeting can be a powerful tool for managing your infrastructure, but it should not be part of your regular workflow. It can cause inconsistencies in your state file and lead to unexpected dependencies or conflicts between resources. It is recommended to use resource targeting only as a last resort when you need to fix a specific problem or test a specific change. For most cases, you should apply your entire plan as defined by your configuration files.
Let's give it a try with our docker example.
First, let's rebuild our infrastructure:
type 'yes' for the prompt.
Now let's change the image_tag from "1.23.3" to "1.23.1" in the terraform.tfvars file.
Run terraform plan again and notice that both the docker_container.nginx_container and the docker_image.nginx_image resources are planned to be destroyed and recreated.
To target only the docker image to be recreated let's use our -target flag:
Notice now that only the image will be updated. Also, notice the warning message about resource targeting being in effect.
You can go ahead and apply the changes with the following command:
Now notice the error message when we try to apply:
This shows the dependency that this container has on the current image tag. That's why the resource targeting capability needs to be used with care.
Best Practices
To make the most of Terraform Plan and Terraform in general, here are some best practices to follow:
Conclusion
Terraform Plan is a powerful and useful command that lets you preview the changes that Terraform will make to your infrastructure before applying them. It helps you validate your configuration, avoid errors and unwanted changes, and collaborate with your team. In this blog post, we learned about the purpose and importance of Terraform Plan, and how to use it in different scenarios. We also saw how to save and apply a plan file, and how to use some of the options and flags that Terraform Plan supports. By using Terraform Plan, you can make your infrastructure as code more reliable, predictable, and transparent.