Terraform Provisioning: Execution Model, Types, and Last-Resort Practices

Terraform excels at declarative infrastructure provisioning, but the boundary between creating resources and configuring them remains a practical gap. Provisioners exist to bridge that gap by executing scripts or commands on local or remote machines as part of resource creation or deletion. They run after a resource is created and are similar in spirit to EC2 instance user data scripts that only run once on creation. If a provisioner fails mid-run, Terraform marks the resource as tainted and will recreate it on the next apply.

Terraform is developed by HashiCorp and is an industry-standard Infrastructure as Code tool used to build, modify, and manage infrastructure safely and efficiently. It automates infrastructure provisioning instead of manual console configuration, enables version control and collaboration, and reduces human errors while improving scalability and consistency. Terraform uses a declarative configuration language, is cloud agnostic, supports immutable infrastructure, manages state, and is modular.

What Provisioners Are and Why They Exist

Terraform provisioners have nothing in common with providers. They allow the execution of various commands or scripts on either local or remote machines, and they can also transfer files from a local environment to a remote one.

Terraform's declarative model excels at resource provisioning, but not always at imperative, in-instance tasks. That is where provisioners come in. Provisioning primarily involves configuration activities performed after the resource is created. It may involve some file operations, executing CLI commands, or even executing the script. Once the resource is successfully initialized, it is ready to accept connections.

Provisioners exist to fill the gap where Terraform creates infrastructure but cannot natively configure what it creates. Once an EC2 instance boots, something still needs to install the web server, write the config files, and run the bootstrap script. Provisioners let you run scripts or copy files on local or remote machines as part of a Terraform apply or destroy operation.

Provisioners in Terraform are blocks that allow you to execute commands or scripts either locally on the machine running Terraform or on a remote machine via SSH/WinRM etc during resource creation or destruction. Terraform provisioners allow you to execute scripts or specific actions on a local or remote machine during a resource's lifecycle, typically after creation or before destruction.

Built-in Provisioner Types

Terraform includes a number of built-in provisioners.

  • file: Copies files or directories from the machine running Terraform to the newly created resource.
  • remote-exec: Executes a command on the newly created resource.
  • local-exec: Executes a command on the machine running Terraform.

Terraform offers three provisioner types:

  • The file provisioner copies files or directories from the machine running Terraform to a remote resource, which is useful for transferring config files, scripts, or binaries at boot time.
  • The local-exec provisioner runs a command or script on the Terraform host itself rather than on any remote resource, making it a good fit for triggering external processes, invoking CLI tools, or writing outputs to local files.
  • The remote-exec provisioner SSHs or WinRMs into a newly created resource and runs commands directly on it, typically to install software or perform initial configuration.

Both file and remote-exec require a connection block that tells Terraform how to reach the target machine. local-exec needs no connection since it runs locally.

Provisioner Execution Target Requires Connection Typical Use
file Remote resource Yes Transfer config files, scripts, or binaries at boot time
remote-exec Remote resource Yes Install software or perform initial configuration
local-exec Machine running Terraform No Trigger external processes, invoke CLI tools, write outputs to local files

Provisioner artifacts are not tracked in Terraform state.

Connection Block and Prerequisites

Provisioning primarily involves configuration activities performed after the resource is created. It may involve some file operations, executing CLI commands, or even executing the script. Once the resource is successfully initialized, it is ready to accept connections.

Both file and remote-exec require a connection block that tells Terraform how to reach the target machine. The connection block is a prerequisite for remote operations.

Terraform executes provisioners in the order they are defined in the configuration file.

Execution Order, Creation-Time vs Destroy-Time

You can add creation and destruction provisioners to the same resource block, but Terraform only runs the provisioners that are valid for a given operation. Terraform runs valid provisioners in the order they're defined in the configuration file.

The following example contains two local-exec provisioners that run in order:

```hcl
resource "aws_instance" "web" {

...

provisioner "local-exec" {
command = "echo first"
}
provisioner "local-exec" {
command = "echo second"
}
}
```

Terraform executes provisioners in the order they are defined in the configuration file.

Creation-time vs destroy-time provisioners are discussed as a core behavior. Terraform only runs the provisioners that are valid for a given operation.

Failure Handling and on_failure

If a provisioner fails mid-run, Terraform marks the resource as tainted and will recreate it on the next apply.

By default, provisioners that fail also cause the terraform apply command to fail. To configure Terraform to continue its operation, set the on_failure argument to continue. Terraform ignores the error and continues the operation.

In the following, Terraform continues to create the web resource even if the echo command fails:

```hcl
resource "aws_instance" "web" {

...

provisioner "local-exec" {
command = "echo The server's IP address is ${self.privateip}"
on
failure = continue
}
}
```

Terraform provisioners are one of the most polarizing features in the Infrastructure as Code toolkit. They exist to bridge the gap between Terraform's declarative model and the messy, imperative reality of running infrastructure. But HashiCorp, who built Terraform, says to use them only as a "last resort."

Common Use Cases and Example Patterns

Provisioners can also be used to implement custom logic, such as:

  • Installing and configuring software
  • Creating and configuring user accounts
  • Starting and stopping services
  • Performing health checks

Provisioners allow the execution of commands or scripts on a local or remote machine during a resource's lifecycle, typically after creation or before destruction.

A typical scenario is installing Nginx on a newly created instance using provisioners.

The guide covers:

  • What is a Terraform provisioner?
  • Terraform provisioner types
  • How to use Terraform provisioners?
  • When to run the provisioners?
  • The connection block
  • Terraform local-exec provisioner
  • Terraform file provisioner
  • Terraform remote-exec provisioner
  • Example – How to install Nginx web server using provisioners
  • Why should provisioners be a last resort?

Why Provisioners Are a Last Resort

Provisioners should be used as a last resort. There are better alternatives for most situations, such as using Terraform modules or a configuration management tool like Ansible or Chef.

Why should provisioners be a last resort? Terraform provisioners are one of the most polarizing features in the Infrastructure as Code toolkit. They exist to bridge the gap between Terraform's declarative model and the messy, imperative reality of running infrastructure. But HashiCorp, who built Terraform, says to use them only as a "last resort."

This pillar article pulls together everything you need to know about Terraform provisioners: what they are, when rarely to use them, why they cause trouble, and what to use instead.

Evaluate alternatives in order before reaching for a provisioner: cloud-init/user-data, Packer images, provider-native resources, then configuration management tools. For resourceless provisioning in new projects, prefer terraformdata over nullresource.

Provisioners also inherit every environment assumption of creation-time provisioners.

Alternatives and Recommendations

Terraform is great at creating infrastructure, but it cannot natively configure what it creates. Once an EC2 instance boots, something still needs to install the web server, write the config files, and run the bootstrap script. Provisioners exist to fill that gap. They let you run scripts or copy files on local or remote machines as part of a Terraform apply or destroy operation.

In this guide we cover how each provisioner type works, walk through real examples, and explain when you should reach for something else instead.

TL;DR:

  • Terraform provisioners run scripts or copy files on local or remote machines during resource creation or destruction.
  • There are three provisioner types available: local-exec, remote-exec, and file.
  • Provisioner artifacts are not tracked in Terraform state.

We recommend using only provisioners built into Terraform, but you can use third-party provisioners as plugins when no other alternative is available.

Best Practices and Maintenance Tips

Best practices and maintenance tips include understanding creation-time vs destroy-time provisioners, failure handling, on_failure, when meta-arguments, advanced scenarios and pitfalls, alternatives and recommendations, complete examples with multiple provisioners, realistic use cases.

This blog explores Terraform provisioners in depth for beginners and advanced users alike. It covers:

  • What provisioners are, and when and when not to use them
  • Types of built-in provisioners
  • Connection blocks, script execution, and subtle behaviors
  • Creation-time vs destroy-time provisioners
  • Failure handling, on_failure, when meta-arguments
  • Advanced scenarios and pitfalls
  • Alternatives and recommendations
  • Complete examples with multiple provisioners
  • Realistic use cases
  • Best practices and maintenance tips

Conclusion

Terraform provisioners provide a narrow, imperative escape hatch inside a declarative workflow. They allow execution of scripts or specific actions on a local or remote machine during a resource's lifecycle, typically after creation or before destruction. The three built-in types, file, remote-exec, and local-exec, each serve distinct targets and connection requirements. File and remote-exec require a connection block, while local-exec runs on the Terraform host.

The execution model is ordered and operation-specific. Terraform executes provisioners in the order they are defined in the configuration file and only runs provisioners valid for the current operation. Creation-time and destroy-time provisioners can coexist on the same resource.

Failure handling is critical. By default a failing provisioner fails the apply and marks the resource as tainted for recreation on the next apply. Setting on_failure to continue allows Terraform to ignore the error and continue the operation.

Despite their utility, provisioners should be used as a last resort. HashiCorp advises using them only when alternatives such as cloud-init/user-data, Packer images, provider-native resources, configuration management tools like Ansible or Chef, or Terraform modules cannot solve the problem. Provisioner artifacts are not tracked in Terraform state, which creates drift and maintenance risk. For resourceless provisioning in new projects, prefer terraformdata over nullresource.

Effective use requires understanding connection prerequisites, execution order, failure semantics, and the maintenance burden of imperative bootstrapping inside declarative code. When used sparingly and with explicit failure policies, provisioners can bridge temporary gaps, but long-term configuration should migrate to purpose-built tools.

Sources

  1. devopsschool.com
  2. spacelift.io
  3. awstip.com
  4. geeksforgeeks.org
  5. scalr.com
  6. developer.hashicorp.com

Related Posts