Terraform
Apply Terraform configuration
The core Terraform workflow consists of three main steps after you have written your Terraform configuration:
- Initialize prepares your workspace so Terraform can apply your configuration.
- Plan allows you to preview the changes Terraform will make before you apply them.
- Apply makes the changes defined by your plan to create, update, or destroy resources.
When you apply changes to your infrastructure, Terraform uses the providers and modules installed during initialization to execute the steps stored in an execution plan. These steps create, update, and delete infrastructure to match your resource configuration.
In this tutorial, you will apply example configuration and review the steps that
Terraform takes to apply changes. You will also learn how Terraform recovers
from errors during apply, and some common ways to use the apply
command.
Prerequisites
You can complete this tutorial using the same workflow with either Terraform Community Edition or HCP Terraform. HCP Terraform is a platform that you can use to manage and execute your Terraform projects. It includes features like remote state and execution, structured plan output, workspace resource summaries, and more.
Select the HCP Terraform tab to complete this tutorial using HCP Terraform.
This tutorial assumes that you are familiar with the Terraform workflow. If you are new to Terraform, complete the Get Started tutorials first.
In order to complete this tutorial, you will need the following:
- Terraform v1.6+ installed locally.
- An AWS account with local credentials configured for use with Terraform.
- The jq command line utility.
Clone the example repository
In your terminal, clone the learn-terraform-apply
repository.
$ git clone https://github.com/hashicorp-education/learn-terraform-apply
Navigate to the cloned repository.
$ cd learn-terraform-apply
Review configuration
The example configuration in this repository creates an EC2 instance through
resources and local and public modules. The modules/aws-ec2-instance
subdirectory contains the local module used to create the instance.
$ tree
.
├── LICENSE
├── README.md
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
Terraform uses the provider versions specified in the terraform.tf
file.
terraform.tf
terraform {
required_version = "~> 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.7.0"
}
random = {
source = "hashicorp/random"
version = "3.5.1"
}
time = {
source = "hashicorp/time"
version = "0.9.1"
}
}
## ...
}
Open the main.tf
file. This file defines configuration for an EC2 instance and an S3 bucket.
main.tf
provider "aws" {
region = var.region
}
provider "random" {}
provider "time" {}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "random_pet" "instance" {
length = 2
}
resource "aws_instance" "main" {
count = 3
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = "${random_pet.instance.id}-${count.index}"
Owner = "${var.project_name}-tutorial"
}
}
resource "aws_s3_bucket" "example" {
tags = {
Name = "Example Bucket"
Owner = "${var.project_name}-tutorial"
}
}
In this example configuration, the aws_instance.main
resource depends on the
random_pet.instance
resource and the aws_ami.ubuntu
data source. When you
apply this configuration, Terraform will create the random_pet
resource and
populate the aws_ami
data source before it creates the instance.
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 them to create your plan.
Initialize the Terraform configuration with terraform init
.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/random from the dependency lock file
- Reusing previous version of hashicorp/time from the dependency lock file
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.7.0
- Using previously-installed hashicorp/random v3.5.1
- Using previously-installed hashicorp/time v0.9.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Apply configuration
Apply the configuration.
$ terraform apply
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-055744c75048d8296]
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.main[0] will be created
+ resource "aws_instance" "main" {
## ...
Plan: 5 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ bucket_name = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Since you did not provide a saved plan, Terraform created a plan and asked you to approve it before making any changes to your resources.
When you approve the plan and apply this configuration, Terraform will:
- Lock your workspace's state, so that no other instances of Terraform will
attempt to modify your state or apply changes to your resources. If Terraform
detects an existing lock file (
.terraform.tfstate.lock.info
), it will report an error and exit. - Create a plan, and wait for you to approve it. Alternatively, you can provide
a saved plan created with the
terraform plan
command, in which case Terraform will not prompt for approval. - Execute the steps defined in the plan using the providers you installed when you initialized your configuration. Terraform executes steps in parallel when possible, and sequentially when one resource depends on another.
- Update your workspace's state with a snapshot of the new state of your resources.
- Unlock your workspace's state.
- Report the changes it made, as well as any output values defined in your configuration.
Respond to the confirmation prompt with a yes
to apply the proposed execution
plan.
##...
Enter a value: yes
random_pet.instance: Creating...
random_pet.instance: Creation complete after 0s [id=dashing-tapir]
aws_s3_bucket.example: Creating...
aws_instance.main[2]: Creating...
aws_instance.main[0]: Creating...
aws_instance.main[1]: Creating...
aws_s3_bucket.example: Creation complete after 2s [id=terraform-20230831212958335300000001]
aws_instance.main[2]: Still creating... [10s elapsed]
aws_instance.main[0]: Still creating... [10s elapsed]
aws_instance.main[1]: Still creating... [10s elapsed]
aws_instance.main[2]: Still creating... [20s elapsed]
aws_instance.main[1]: Still creating... [20s elapsed]
aws_instance.main[0]: Still creating... [20s elapsed]
aws_instance.main[0]: Still creating... [30s elapsed]
aws_instance.main[1]: Still creating... [30s elapsed]
aws_instance.main[2]: Still creating... [30s elapsed]
aws_instance.main[2]: Creation complete after 33s [id=i-0c8c41a0cde73a0a3]
aws_instance.main[1]: Still creating... [40s elapsed]
aws_instance.main[0]: Still creating... [40s elapsed]
aws_instance.main[1]: Creation complete after 43s [id=i-0ed29492e0729f3ee]
aws_instance.main[0]: Still creating... [50s elapsed]
aws_instance.main[0]: Creation complete after 53s [id=i-0a612c0bb446b46f4]
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
bucket_name = "terraform-20230831212958335300000001"
When you applied the example configuration, Terraform created the random pet name and image resources first, and then created the four containers which depend on them in parallel. When Terraform creates a plan, it analyzes the dependencies between your resources so that it makes changes to your resources in the correct order, and in parallel when possible. When it applies your configuration, Terraform reports its progress as it creates, updates, and deletes your resources.
Errors during apply
When Terraform encounters an error during an apply step, it will:
- Log the error and report it to the console.
- Update the state file with any changes to your resources.
- Unlock the state file.
- Exit.
Your infrastructure may be in an invalid state after a Terraform apply step errors out. Terraform does not support automatically rolling back a partially-completed apply. After you resolve the error, you must apply your configuration again to update your infrastructure to the desired state.
To review how Terraform handles errors, introduce an intentional error during an apply.
Add the following configuration to main.tf
to create a new S3 object.
main.tf
resource "aws_s3_object" "example" {
bucket = aws_s3_bucket.example.bucket
key = "README.md"
source = "./README.md"
etag = filemd5("./README.md")
}
Create a saved plan for the new configuration.
$ terraform plan -out "add-object"
random_pet.instance: Refreshing state... [id=dashing-tapir]
data.aws_ami.ubuntu: Reading...
aws_s3_bucket.example: Refreshing state... [id=terraform-20230831212958335300000001]
data.aws_ami.ubuntu: Read complete after 1s [id=ami-055744c75048d8296]
aws_instance.main[2]: Refreshing state... [id=i-0c8c41a0cde73a0a3]
aws_instance.main[0]: Refreshing state... [id=i-0a612c0bb446b46f4]
aws_instance.main[1]: Refreshing state... [id=i-0ed29492e0729f3ee]
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_s3_object.example will be created
+ resource "aws_s3_object" "example" {
+ bucket = "terraform-20230831212958335300000001"
+ bucket_key_enabled = (known after apply)
+ content_type = (known after apply)
+ etag = "87729e4959c183dac2e9382a4d6819e6"
+ force_destroy = false
+ id = (known after apply)
+ key = "README.md"
+ kms_key_id = (known after apply)
+ server_side_encryption = (known after apply)
+ source = "./README.md"
+ storage_class = (known after apply)
+ tags_all = (known after apply)
+ version_id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: add-object
To perform exactly these actions, run the following command to apply:
terraform apply "add-object"
Now, remove the bucket outside of Terraform. This will cause Terraform to error when you apply the plan since the plan assumes the bucket exists. Use either the AWS Console or the AWS CLI to remove the bucket.
$ aws s3 rb s3://$(terraform output -raw bucket_name)
remove_bucket: terraform-20230821193112318500000003
Now apply the plan. Terraform will error out when it tries to create the
aws_s3_object.example
object.
$ terraform apply "add-object"
aws_s3_object.example: Creating...
╷
│ Error: uploading object to S3 bucket (terraform-20230831212958335300000001): NoSuchBucket: The specified bucket does not exist
│ status code: 404, request id: EM3DTWS9ZNWXZN6W, host id: OH/LJiviqHociAxxd3rZjUcSpysbAT5lfERQLk6rDbPTW4xzZl8mYDdUwZtI41A2Pwi2arR0fGE=
│
│ with aws_s3_object.example,
│ on main.tf line 51, in resource "aws_s3_object" "example":
│ 51: resource "aws_s3_object" "example" {
│
╵
Because you removed the S3 bucket after you created the plan, AWS was unable to create the object, so the AWS provider reported the error to Terraform.
Common reasons for apply errors include:
- A change to a resource outside of Terraform's control.
- Networking or other transient errors.
- An expected error from the upstream API, such as a duplicate resource name or reaching a resource limit.
- An unexpected error from the upstream API, such as an internal server error.
- A bug in the Terraform provider code, or Terraform itself.
Depending on the cause of the error, you may need to resolve the underlying issue by either modifying your configuration or diagnosing and resolving the error from the cloud provider API. Your Terraform project is still tracking the image resource because Terraform has not yet refreshed your resource's state.
Print out the state of your S3 bucket with terraform show
.
$ terraform show -json | jq '.values.root_module.resources[] | select( .address == "aws_s3_bucket.example")'
{
"address": "aws_s3_bucket.example",
"mode": "managed",
"type": "aws_s3_bucket",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/aws",
"schema_version": 0,
"values": {
"acceleration_status": "",
"acl": null,
"arn": "arn:aws:s3:::terraform-20230831212958335300000001",
"bucket": "terraform-20230831212958335300000001",
### ...
"tags": {},
"tags_all": {},
"versioning": [
{}
],
"website": []
}
}
Terraform stores it's current understanding of the state of your resources,
either locally in the terraform.tfstate
file, or on a remote backend such as
HCP Terraform. You can use the terraform show
command to print out your
state. This command does not refresh your state, so the information in your
state can be out of date. In this case, your project's state reports the
existence of the S3 bucket you manually deleted earlier in this tutorial. When
you apply a plan, Terraform will only make changes defined in the plan. Because
of this, changes to your resources between the time you plan you changes and
attempt to apply them can cause Terraform to error if the plan can no longer be
applied as writted. To resolve this error, you must create and apply a new plan
that takes the missing bucket into account.
The next time you plan a change to this project, Terraform will update the
current state of your resources from the underlying APIs using the providers you
have installed. Terraform will notice that the bucket represented by the
aws_s3_bucket.example
resource no longer exists, and generate a plan to create
it before creating the new aws_s3_object.example
object.
Apply your configuration. Terraform will referesh your workspace's state to reflect the fact that the S3 bucket no longer exists. Next it will create a plan to reconcile your configuration with that state by creating both the S3 bucket and object. Resources can change outside of Terraform's control for any number of reasons. In most cases, Terraform can handle these differences automatically by creating, destroying, or updating resources to make them match your configuration. Terraform will create a plan to do so, and wait for you to confirm it.
$ terraform apply
## ...
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may
have affected this plan:
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_s3_bucket.example has been deleted
- resource "aws_s3_bucket" "example" {
- bucket = "terraform-20230831212958335300000001" -> null
id = "terraform-20230831212958335300000001"
tags = {
"Name" = "Example Bucket"
"Owner" = "terraform-apply-tutorial"
}
# (10 unchanged attributes hidden)
# (3 unchanged blocks hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
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_s3_bucket.example will be created
+ resource "aws_s3_bucket" "example" {
## ...
}
# aws_s3_object.example will be created
+ resource "aws_s3_object" "example" {
+ bucket = (known after apply)
## ...
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
~ bucket_name = "terraform-20230831212958335300000001" -> (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Respond to the confirmation prompt with a yes
to provision the S3 bucket and
object.
Enter a value: yes
aws_s3_bucket.example: Creating...
aws_s3_bucket.example: Creation complete after 1s [id=terraform-20230831213755902000000001]
aws_s3_object.example: Creating...
aws_s3_object.example: Creation complete after 1s [id=README.md]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
bucket_name = "terraform-20230831213755902000000001"
In this case, you were able to recover from the error by re-applying your configuration. Depending on the underlying cause of the error, you may need to resolve the error outside of Terraform or by changing your Terraform configuration. For example, if Terraform reports a resource limit error from your cloud provider's API, you may need to work with your cloud provider to increase that limit before applying your configuration.
Replace Resources
When using Terraform, you will usually apply an entire configuration change at
once. Terraform and its providers will determine the changes to make and the
order to make them in. However, there are some cases where you may need to
replace or modify individual resources. Terraform provides two arguments to the
plan
and apply
commands that allow you to interact with specific resources:
-replace
and -target
.
Use the -replace
argument when a resource has become unhealthy or stops
working in ways that are outside of Terraform's control. For instance, an error
in your EC2 instance's OS configuration could require that the instance be
replaced. There is no corresponding change to your Terraform configuration, so
you want to instruct Terraform to reprovision the resource using the same
configuration.
The -replace
argument requires a resource address. List the resources in your
configuration with terraform state list
.
$ terraform state list
data.aws_ami.ubuntu
aws_instance.main[0]
aws_instance.main[1]
aws_instance.main[2]
aws_s3_bucket.example
aws_s3_object.example
random_pet.instance
Replace the second EC2 instance. Respond to the confirmation prompt with a
yes
.
$ terraform apply -replace "aws_instance.main[1]"
random_pet.instance: Refreshing state... [id=dashing-tapir]
data.aws_ami.ubuntu: Reading...
aws_s3_bucket.example: Refreshing state... [id=terraform-20230831213755902000000001]
data.aws_ami.ubuntu: Read complete after 1s [id=ami-055744c75048d8296]
aws_instance.main[0]: Refreshing state... [id=i-0a612c0bb446b46f4]
aws_instance.main[2]: Refreshing state... [id=i-0c8c41a0cde73a0a3]
aws_instance.main[1]: Refreshing state... [id=i-0ed29492e0729f3ee]
aws_s3_object.example: Refreshing state... [id=README.md]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_instance.main[1] will be replaced, as requested
-/+ resource "aws_instance" "main" {
~ arn = "arn:aws:ec2:us-east-1:841397984957:instance/i-0ed29492e0729f3ee" -> (known after apply)
## ...
Plan: 1 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.main[1]: Destroying... [id=i-0ed29492e0729f3ee]
aws_instance.main[1]: Still destroying... [id=i-0ed29492e0729f3ee, 10s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0ed29492e0729f3ee, 20s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0ed29492e0729f3ee, 30s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0ed29492e0729f3ee, 40s elapsed]
aws_instance.main[1]: Destruction complete after 40s
aws_instance.main[1]: Creating...
aws_instance.main[1]: Still creating... [10s elapsed]
aws_instance.main[1]: Still creating... [20s elapsed]
aws_instance.main[1]: Still creating... [30s elapsed]
aws_instance.main[1]: Still creating... [40s elapsed]
aws_instance.main[1]: Creation complete after 42s [id=i-0cb93ff7e726c46cf]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Outputs:
bucket_name = "terraform-20230831213755902000000001"
The second case where you may need to partially apply configuration is when
troubleshooting an error that prevents Terraform from applying your entire
configuration at once. This type of error may occur when a target API or
Terraform provider error leaves your resources in an invalid state that
Terraform cannot resolve automatically. Use the -target
command line argument
when you apply to target individual resources rather than apply the entire
configuration. Refer to the Target
resources tutorial for more
information.
Clean up infrastructure
Now that you have learned how Terraform applies changes to your infrastructure,
remove the resources you provisioned in this tutorial. Confirm the operation
with a yes
.
$ terraform destroy
random_pet.instance: Refreshing state... [id=meet-dassie]
data.aws_ami.ubuntu: Reading...
aws_s3_bucket.example: Refreshing state... [id=terraform-20230901173128874100000001]
data.aws_ami.ubuntu: Read complete after 1s [id=ami-055744c75048d8296]
aws_instance.main[2]: Refreshing state... [id=i-0e0694473a240c3db]
aws_instance.main[1]: Refreshing state... [id=i-0e68bfd135ef460d9]
aws_instance.main[0]: Refreshing state... [id=i-0bba2e53d1d7355cd]
aws_s3_object.example: Refreshing state... [id=README.md]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.main[0] will be destroyed
- resource "aws_instance" "main" {
- ami = "ami-055744c75048d8296" -> null
## ...
Plan: 0 to add, 0 to change, 6 to destroy.
Changes to Outputs:
- bucket_name = "terraform-20230901173128874100000001" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_s3_object.example: Destroying... [id=README.md]
aws_instance.main[0]: Destroying... [id=i-0bba2e53d1d7355cd]
aws_instance.main[2]: Destroying... [id=i-0e0694473a240c3db]
aws_instance.main[1]: Destroying... [id=i-0e68bfd135ef460d9]
aws_s3_object.example: Destruction complete after 0s
aws_s3_bucket.example: Destroying... [id=terraform-20230901173128874100000001]
aws_s3_bucket.example: Destruction complete after 1s
aws_instance.main[0]: Still destroying... [id=i-0bba2e53d1d7355cd, 10s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0e68bfd135ef460d9, 10s elapsed]
aws_instance.main[2]: Still destroying... [id=i-0e0694473a240c3db, 10s elapsed]
aws_instance.main[0]: Still destroying... [id=i-0bba2e53d1d7355cd, 20s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0e68bfd135ef460d9, 20s elapsed]
aws_instance.main[2]: Still destroying... [id=i-0e0694473a240c3db, 20s elapsed]
aws_instance.main[1]: Still destroying... [id=i-0e68bfd135ef460d9, 30s elapsed]
aws_instance.main[0]: Still destroying... [id=i-0bba2e53d1d7355cd, 30s elapsed]
aws_instance.main[2]: Still destroying... [id=i-0e0694473a240c3db, 30s elapsed]
aws_instance.main[2]: Destruction complete after 30s
aws_instance.main[0]: Destruction complete after 31s
aws_instance.main[1]: Still destroying... [id=i-0e68bfd135ef460d9, 40s elapsed]
aws_instance.main[1]: Destruction complete after 41s
random_pet.instance: Destroying... [id=meet-dassie]
random_pet.instance: Destruction complete after 0s
Destroy complete! Resources: 6 destroyed.
The terraform destroy
command is a shortcut which creates a destroy plan to
remove all of your resources, waits for your confirmation, and then applies the
plan.
Next steps
In this tutorial, you learned how Terraform applies changes to your infrastructure. You also reviewed how Terraform handles errors by reproducing an error during the apply step. Check out the following resources to learn more about managing your Terraform projects:
- Learn how to Customize Terraform Configuration with Variables.
- Learn how to work on Terraform projects with your team with the Store Remote State tutorial and the HCP Terraform get started tutorials.
- Learn about Running Terraform in Automation.