Terraform -target: Plan, Apply or Destroy Specific Resources

Use Terraform -target to plan, apply or destroy specific resources and modules, with real plan output, -replace, count indexes and OpenTofu's -exclude.

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 -target for 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_bucket is the module block’s name in the calling configuration (not the module’s source directory).
  • local_file.bucket is 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=... or terraform validate -target=...: neither command accepts -target. Only plan, apply, destroy and refresh do.
  • Using the module’s directory name: the address uses the name in the module "<name>" block, not source.
  • Unquoted brackets: -target=aws_instance.web[0] can fail in zsh with no matches found. Quote it.
  • Forgetting the full plan afterwards: always run an untargeted terraform plan once the emergency is over and make sure it is clean.

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.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy