Terraform allows you to target specific resources when you plan, apply, or destroy your infrastructure. You can use Terraform’s -target option to target specific resources, modules, or collections of resources.
The option instructs Terraform to focus its planning, apply or destroy efforts only on the resource instances that match the given address and on any objects those instances depend on. That last part matters, and the examples below show it.
Targeting is useful when your configuration contains lots of resources and you do not want to apply all of it: you just want to run one specific resource, or a few, out of your Terraform configuration.
Everything here works the same with OpenTofu: replace terraform with tofu. The output in this guide was captured with OpenTofu 1.11, which prints OpenTofu where Terraform prints Terraform; the plan format is otherwise the same, though the wording of some warnings differs (Terraform’s targeting warning does not mention -exclude, which is OpenTofu-only).
Targeting is for exceptions, not routine use
Before the how-to, the warning. Every targeted plan prints this:
Warning: Resource targeting is in effect
You are creating a plan with either the -target option or the -exclude
option, which means that the result of this plan may not represent all of the
changes requested by the current configuration.
The -target and -exclude options are not for routine use, and are provided
only for exceptional situations such as recovering from errors or mistakes,
or when OpenTofu specifically suggests to use it as part of an error message.
Take it seriously. A targeted apply leaves your state out of step with your configuration until you run a full plan/apply again. Good reasons to target:
- Recovering from a failed apply, or breaking a dependency cycle Terraform cannot resolve on its own.
- Creating one resource first because another one needs its value at plan time (Terraform’s own error messages suggest
-targetfor this). - Rebuilding or destroying a single broken resource without touching anything else.
If you find yourself targeting the same group of resources every day, that group probably wants to be its own root module with its own state.
The example configuration
To produce real output without a cloud account, this guide uses the local provider and the built-in terraform_data resource (Terraform 1.4+ and all OpenTofu versions). The addresses work exactly the same for aws_instance, google_container_cluster or anything else.
main.tf:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "local_file" "app_config" {
filename = "${path.module}/out/app.conf"
content = "version = ${terraform_data.app_version.output}\n"
}
resource "local_file" "db_config" {
filename = "${path.module}/out/db.conf"
content = "host = 10.0.1.10\n"
}
resource "terraform_data" "app_version" {
input = "1.0.0"
}
resource "terraform_data" "worker" {
count = 2
input = "worker-${count.index}"
}
module "uat_files_bucket" {
source = "./modules/bucket"
name = "uat-files"
}
module "prod_files_bucket" {
source = "./modules/bucket"
name = "prod-files"
}
modules/bucket/main.tf, a stand-in for an S3 bucket module:
variable "name" {
type = string
}
resource "local_file" "bucket" {
filename = "${path.root}/out/${var.name}.bucket"
content = "bucket = ${var.name}\n"
}
resource "terraform_data" "policy" {
input = "${var.name}-policy"
}
Initialize it once. init does not take -target: it always installs every provider and module the configuration uses.
terraform init
Target a single resource
The address is <resource_type>.<name>:
terraform plan -target=local_file.app_config
app_config reads terraform_data.app_version, so Terraform pulls the dependency in too, even though you only named one resource:
# local_file.app_config will be created
# terraform_data.app_version will be created
Plan: 2 to add, 0 to change, 0 to destroy.
Dependencies are always included. Dependents (things that read the targeted resource) are not, which is why a targeted apply can leave them stale.
Target a resource inside a module
Prefix the address with module.<module_name>.:
terraform plan -target=module.uat_files_bucket.local_file.bucket
Where:
module.uat_files_bucketis themoduleblock’s name in the calling configuration (not the module’s source directory).local_file.bucketis the resource type and name inside the module.
To target everything in a module, stop at the module address:
terraform plan -target=module.uat_files_bucket
# module.uat_files_bucket.local_file.bucket will be created
# module.uat_files_bucket.terraform_data.policy will be created
Plan: 2 to add, 0 to change, 0 to destroy.
Target multiple resources
Repeat -target, mixing root resources and module resources freely:
terraform plan \
-target=module.uat_files_bucket.local_file.bucket \
-target=local_file.db_config
# local_file.db_config will be created
# module.uat_files_bucket.local_file.bucket will be created
Plan: 2 to add, 0 to change, 0 to destroy.
With many addresses, OpenTofu also accepts -target-file=targets.txt (one address per line).
Target a count or for_each instance
Instances have an index in square brackets. Quote the whole address so your shell does not treat the brackets as a glob:
terraform plan -target='terraform_data.worker[1]'
# terraform_data.worker[1] will be created
Plan: 1 to add, 0 to change, 0 to destroy.
For for_each, the key is a string, so it needs double quotes inside the single quotes, for example -target='module.bucket["uat"]' or -target='aws_instance.web["api"]'.
Example with S3 bucket modules: plan and apply with a saved plan
Plan both bucket modules, save the plan, then apply exactly that plan. Bash brace expansion gives you two -target flags in one go:
terraform plan -target=module.{uat,prod}_files_bucket -out=tf.plan
which the shell expands to:
terraform plan -target=module.uat_files_bucket -target=module.prod_files_bucket -out=tf.plan
# module.prod_files_bucket.local_file.bucket will be created
# module.prod_files_bucket.terraform_data.policy will be created
# module.uat_files_bucket.local_file.bucket will be created
# module.uat_files_bucket.terraform_data.policy will be created
Plan: 4 to add, 0 to change, 0 to destroy.
Saved the plan to: tf.plan
The targets are recorded in the saved plan, so apply does not need them again:
terraform apply tf.plan
module.prod_files_bucket.terraform_data.policy: Creating...
module.uat_files_bucket.terraform_data.policy: Creating...
module.prod_files_bucket.local_file.bucket: Creating...
module.uat_files_bucket.local_file.bucket: Creating...
module.prod_files_bucket.terraform_data.policy: Creation complete after 0s [id=f1a2bda7-e770-cb9d-e2fa-c88420b123f2]
module.uat_files_bucket.terraform_data.policy: Creation complete after 0s [id=77a7fe95-e8a5-e46d-acfa-65227d716acd]
module.uat_files_bucket.local_file.bucket: Creation complete after 0s [id=c11f1ee7d6dfb956552ec0f6b8c1775d1de9e2d6]
module.prod_files_bucket.local_file.bucket: Creation complete after 0s [id=a8f1163f04d698706a95ed239ee60bedd16d5427]
Warning: Applied changes may be incomplete
The plan was created with the -target or the -exclude option in effect, so
some changes requested in the configuration may have been ignored and the
output values may not be fully updated. Run the following command to verify
that no other changes are pending:
tofu plan
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
A plain terraform plan afterwards shows what the targeted apply skipped:
# local_file.app_config will be created
# local_file.db_config will be created
# terraform_data.app_version will be created
# terraform_data.worker[0] will be created
# terraform_data.worker[1] will be created
Delete tf.plan once it is applied: a saved plan can contain sensitive values in cleartext, so keep it out of Git.
Replace (recreate) one resource with -replace
terraform taint is deprecated. To force one resource to be destroyed and recreated, use -replace:
terraform plan -replace='module.uat_files_bucket.terraform_data.policy'
On its own, -replace is not a target: the plan also includes every other pending change in the configuration. To replace one resource and do nothing else, combine it with -target:
terraform plan \
-replace='module.uat_files_bucket.terraform_data.policy' \
-target='module.uat_files_bucket.terraform_data.policy'
# module.uat_files_bucket.terraform_data.policy will be replaced, as requested
-/+ resource "terraform_data" "policy" {
~ id = "77a7fe95-e8a5-e46d-acfa-65227d716acd" -> (known after apply)
~ output = "uat-files-policy" -> (known after apply)
# (1 unchanged attribute hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
Destroy a specific resource or module
-target works with destroy (or plan -destroy to preview):
terraform plan -destroy -target=module.prod_files_bucket
# module.prod_files_bucket.local_file.bucket will be destroyed
# module.prod_files_bucket.terraform_data.policy will be destroyed
Plan: 0 to add, 0 to change, 2 to destroy.
Destroy runs the dependency graph the other way round: destroying a resource also destroys everything that depends on it. Targeting terraform_data.app_version takes app_config with it:
terraform plan -destroy -target=terraform_data.app_version
# local_file.app_config will be destroyed
# terraform_data.app_version will be destroyed
Plan: 0 to add, 0 to change, 2 to destroy.
Always read the destroy plan before running terraform destroy -target=....
If you want a resource gone from state but still running in the cloud (for example, to hand it to another configuration), that is terraform state rm, or a removed block in Terraform 1.7+ and OpenTofu 1.7+, not a targeted destroy.
OpenTofu: exclude instead of target
OpenTofu 1.9+ adds the inverse, -exclude: plan everything except the given address and whatever depends on it. It cannot be combined with -target.
tofu plan -exclude=module.prod_files_bucket
-exclude carries the same “exceptional use only” warning as -target.
Common mistakes
terraform init -target=...orterraform validate -target=...: neither command accepts-target. Onlyplan,apply,destroyandrefreshdo.- Using the module’s directory name: the address uses the name in the
module "<name>"block, notsource. - Unquoted brackets:
-target=aws_instance.web[0]can fail in zsh withno matches found. Quote it. - Forgetting the full plan afterwards: always run an untargeted
terraform planonce the emergency is over and make sure it is clean.
Related content
- Terraform AWS VPC with Public and Private subnets with NAT
- How to use terraform to Launch an AWS EC2 Instance
- Create an RDS instance in terraform with a Mariadb Example
- Create AWS VPC Peering in Same Account/Region with Terraform
Conclusion
In this guide, we learnt how to use Terraform targets to make changes to selected resources: single resources, module resources, whole modules and count/for_each instances, plus -replace, targeted destroys, and OpenTofu’s -exclude. Use them to get out of trouble, then run a full plan to bring state and configuration back in line.