Skip to main content

Implementing Provisioners

1. Using Local-Exec Provisioner

Attribute Values Description
command string Required. The command to execute. Must be a string or a list of strings.
interpreter list(string) Optional. A list of strings to use as the interpreter for the command.
environment map(string) Optional. A map of environment variables to set during execution.
lifecycle object Optional. Controls when the provisioner runs (e.g., create_before_destroy).

Example: Running a local command

resource "null_resource" "local_exec_example" {
  provisioner "local-exec" {
    command = "echo 'Hello from local exec'"
  }
}

Example: Setting environment variables

resource "null_resource" "env_example" {
  provisioner "local-exec" {
    command = "echo $MY_VAR"
    environment = {
      MY_VAR = "Hello World"
    }
  }
}
Note: The local-exec provisioner runs on the machine where Terraform is executed, not on the target resource.

2. Using Remote-Exec Provisioner

Property Description Values
connection Connection configuration for remote execution object
command Command to execute on remote machine string
inline Inline script to execute list(string)
interpreter Interpreter to use for inline scripts string
environment Environment variables to set for the command map(string)

Example: Execute a script remotely

resource "null_resource" "remote_exec" {
  provisioner "remote-exec" {
    connection {
      type        = "ssh"
      user        = "ubuntu"
      host        = "${aws_instance.example.public_ip}"
      private_key = "${file("~/.ssh/id_rsa")}"
    }

    inline = [
      "echo 'Hello, World!' > /tmp/hello.txt",
      "chmod +x /tmp/hello.txt"
    ]
  }
}
Note: The remote-exec provisioner is useful for running commands on a remote machine after it has been created. It supports various connection types like SSH and WinRM.
Warning: Remote-exec provisioners are not idempotent and can cause issues if Terraform state is modified or the resource is recreated. Use with caution in production environments.

3. Using File Provisioner

Property Description Example
source Path to the local file or directory to copy ./files/config.json
destination Path on the remote instance where the file will be copied /etc/app/config.json
content Direct content to write to the file (alternative to source) <?xml version="1.0"?><config><port>8080</port></config>

Example: Copying a file to remote instance

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  provisioner "file" {
    source      = "./app-config.json"
    destination = "/etc/app/config.json"
  }
}

Example: Writing content directly to a file

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  provisioner "file" {
    content = <<-EOF
<?xml version="1.0"?>
<config>
  <port>8080</port>
  <host>localhost</host>
</config>
EOF
    destination = "/etc/app/config.xml"
  }
}
Note: The file provisioner is executed during resource creation and destruction. It does not support lifecycle rules like ignore_changes.

4. Configuring Connection Blocks

Property Description Default Example
type Connection type (e.g., ssh, winrm) ssh type = "ssh"
host Host address to connect to None host = "192.168.1.10"
user Username for connection root user = "admin"
port Port to connect on 22 (SSH) port = 2222
private_key Path to private key file None private_key = "/path/to/key"
password Password for authentication None password = "secret"
timeout Timeout for connection in minutes 5m timeout = "10m"

Example: SSH Connection Configuration

connection {
  type        = "ssh"
  host        = "${aws_instance.example.private_ip}"
  user        = "ubuntu"
  port        = 22
  private_key = "<%= File.read("~/.ssh/id_rsa") %>"
  timeout     = "10m"
}
Note: The connection block is used to configure how Terraform connects to the provisioned resource. It's typically used in conjunction with provisioner blocks.
Warning: Avoid storing sensitive credentials like passwords directly in Terraform configuration files. Use environment variables or secrets management tools.

5. Setting Provisioner Timing

Property Description Values Notes
when Determines when the provisioner runs create, destroy Defaults to create. Set to destroy to run during resource destruction.
on_failure Controls behavior if provisioner fails continue, fail Defaults to fail. Set to continue to allow resource creation to proceed despite failure.

Example: Setting provisioner timing

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo 'Instance created'"
    when    = "create"
  }

  provisioner "local-exec" {
    command = "echo 'Instance destroyed'"
    when    = "destroy"
  }
}

6. Handling Provisioner Failures

Failure Type Description Behavior Solution
provisioner failure When a provisioner fails during execution Stops further provisioning and marks resource as failed Use on_failure = "continue" to proceed with other provisioners
timeout Provisioner exceeds configured timeout Interrupts and fails the operation Adjust timeout attribute or use retry logic
script error Script returns non-zero exit code Halts execution and logs error Ensure scripts are idempotent and handle errors gracefully

Example: Using on_failure to continue on error

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo 'First provisioner'"
  }

  provisioner "local-exec" {
    command = "exit 1"
    on_failure = "continue"
  }

  provisioner "local-exec" {
    command = "echo 'Second provisioner'"
  }
}
Note: Setting on_failure = "continue" allows other provisioners to run even if one fails, but it does not prevent the overall resource creation from failing.
Warning: Using on_failure = "continue" can mask critical errors, leading to inconsistent state or partial configuration.

Best Practices for Provisioner Failures

  • Use on_failure = "continue" only when appropriate
  • Implement proper logging and monitoring for provisioner errors
  • Ensure provisioners are idempotent to avoid issues on retries
  • Validate scripts and commands before deploying in production

7. Using Null Resource

The null_resource is a special resource in Terraform that has no provider and does not manage any infrastructure. It's used to execute provisioners when other resources don't support them or for triggering actions after resource creation.

Property Description Example
triggers Map of values that, when changed, will cause the null resource to be recreated.
{
  "key": "value"
}
provisioners Configuration block to define provisioners (e.g., local-exec, remote-exec).
{
  provisioner "local-exec" {
    command = "echo 'Resource created'"
  }
}

Example: Using null_resource with local-exec provisioner

resource "null_resource" "example" {
  triggers = {
    instance_id = aws_instance.example.id
  }

  provisioner "local-exec" {
    command = "echo 'Instance ID is ${self.triggers.instance_id}'"
  }
}
Note: The null_resource is not intended for managing infrastructure but rather for executing actions or triggering workflows.
Warning: Provisioners should be used sparingly as they can make state management more complex and reduce the idempotency of your configurations.

8. Using terraform_data Resource

The terraform_data resource is a special-purpose resource in Terraform designed to manage data that is not directly tied to infrastructure but needs to be tracked and managed by Terraform.

Attribute Description Required?
count Number of instances to create. No
for_each Map or set of values to iterate over. No
lifecycle Customizes resource lifecycle behavior. No
triggers Map of values that, when changed, trigger a refresh or recreation. No

Example: Using terraform_data to trigger a refresh

resource "terraform_data" "example" {
  triggers = {
    timestamp = timestamp()
  }
}

Example: Using terraform_data to manage a file

resource "terraform_data" "file_content" {
  content = "Hello, World!"
  file_name = "output.txt"
}
Note: The terraform_data resource is not intended for managing infrastructure. It's used primarily for data management and triggering actions.

9. Using Time Provider

Property Description Values Notes
trigger Triggers the time provider to update string Used to force a refresh of the time
offset Offset in seconds to add to the current time number Can be positive or negative

Example: Using Time Provider

resource "time_static" "example" {
  triggers = {
    timestamp = "${timestamp()}"
  }
}

resource "time_offset" "example" {
  offset = 3600
  triggers = {
    timestamp = "${timestamp()}"
  }
}

10. Understanding Provisioner Limitations

Limitation Description Impact
State Management Provisioners run during apply and can't be easily re-executed without re-creating resources Changes to provisioner logic may not be reflected in state, leading to inconsistent resource states
Idempotency Provisioners are not guaranteed to be idempotent Re-running provisioners can cause unintended side effects or errors if they modify external systems
Error Handling Errors in provisioners halt the entire apply process Resource creation or update fails entirely if any provisioner encounters an error
Security Provisioners can execute arbitrary commands or scripts Potential security risks if sensitive data (like passwords) are exposed in logs or configuration
Scalability Provisioners run sequentially per resource Can lead to long apply times in large-scale deployments where many resources require provisioners
Note: Provisioners should be used sparingly and only when necessary, as they reduce the declarative nature of Terraform.

Was this article helpful?