September 30, 2020·4 min read·engineeringterraformprovisionerslifecycle

Terraform begins retiring vendor-specific provisioners

Terraform 0.13.4 deprecates four built-in configuration-management provisioners. Their lifecycle limitations explain why image building and boot-time configuration are the preferred alternatives.

A provisioner can execute a command that Terraform cannot describe in its plan. That flexibility creates an awkward boundary in a declarative infrastructure tool. Terraform can know that a machine exists while remaining unable to model what an arbitrary configuration-management command changed inside it.

The September 30 release begins deprecating the built-in habitat, puppet, chef and salt-masterless provisioners. The versioned changelog announces removal in a future version. Deprecation is the event here, and the affected provisioners remain a migration concern before that eventual removal.

I value Terraform most when its declared objects correspond to infrastructure operations it can plan and manage. Vendor-specific command execution weakens that correspondence. The deprecation makes sense in the context of limitations that the existing provisioner documentation already explains.

Why a successful command is insufficient evidence

The 0.13.4 provisioner documentation describes provisioners as a pragmatic last resort. They can perform arbitrary actions, which prevents Terraform from modeling those actions as part of its plan. Remote execution can also require direct network access and login credentials for the new server.

Those requirements introduce a second operational path. Creating a machine through its infrastructure API and reaching that machine through SSH or WinRM are different capabilities. A successful API operation does not imply that the process running Terraform can reach the guest operating system.

More fundamentally, the provisioner's result does not become a set of independently managed Terraform objects. A configuration-management run may change many files and services. Terraform cannot infer their later drift or plan individual corrections merely because it invoked the command that first configured them.

The documentation's preference for provider functionality follows from this distinction. Where a provider models an operation directly, Terraform can participate in managing subsequent changes. Calling the remote system's CLI through local-exec does not supply that same resource model.

Creation-time execution has a narrow lifecycle

By default, provisioners execute during creation. The documentation explicitly distinguishes creation from updates. A configuration that changes after initial creation does not acquire continuous configuration management simply because a provisioner block is present.

Failure also affects the parent resource. A failed creation-time provisioner marks it tainted, so the next apply plans destruction and recreation. Terraform uses that response because an arbitrary failed command can leave the machine partly configured, and Terraform cannot reason precisely about how to finish the command's work.

The alternative on_failure = continue changes whether Terraform continues after the error. It does not make the command's intended changes happen. Treating that setting as a repair would confuse accepting a failure with resolving its consequences.

This is a substantial lifecycle policy to attach to a configuration-management command. Replacing a machine may be appropriate after an incomplete bootstrap, but the reason is Terraform's limited knowledge of the command's effects. The provisioner has not exposed a smaller unit of repair.

Image construction moves repeatable work earlier

The documented alternative for system configuration is a custom image build. The provisioner guide points to Packer and its configuration-management provisioners as a way to install software before producing a deployable system image.

This separates preparation of a reusable image from creation of each machine. The resulting machine starts from the prepared disk contents, so Terraform does not need to repeat every installation action through a remote session during resource creation.

Registration with a centralized configuration-management server can still depend on the individual machine. The guide recommends delaying that registration until boot and passing the necessary information through the platform's supported metadata or user-data mechanism.

That arrangement gives shared preparation and per-machine identity different stages. It avoids baking a machine-specific registration into an image intended for many instances. The infrastructure configuration can provide the boot-time information without becoming the remote command runner.

Boot-time configuration supports independently launched machines

The provisioner guide lists platform mechanisms such as EC2 user data and Azure custom data. It also notes that many distribution images include cloud-init, which can process boot-time data and perform initial configuration without an SSH connection from Terraform.

The architectural benefit becomes clearer for automatically managed groups. Their platform can create new machines while Terraform is not running. A bootstrap process requiring Terraform to log in after every creation cannot cover those independent launches.

I prefer this placement of responsibility because it matches who creates the machine. A platform launching an instance supplies the boot data, and the instance processes that information as it starts. Terraform can declare the launch configuration without needing to participate in every future launch.

Destruction adds another constraint

Destroy-time provisioners run before the resource is destroyed and must remain in configuration to execute. Removing the resource block also removes its provisioner configuration. The documentation therefore describes a staged removal using count = 0 before deleting the block.

Failed destroy-time provisioners can run again during a subsequent apply. Their commands must tolerate repetition, and tainted resources do not run destroy-time provisioners at all. These rules are important when evaluating whether an external cleanup action belongs in this lifecycle.

The September deprecation affects named vendor integrations, while the broader documentation explains the underlying design preference. Image preparation and boot-time mechanisms place configuration work where it can operate independently of a Terraform session. Provider resources remain preferable when the target operation has an infrastructure model that Terraform can manage over time.