---
page_title: Upgrading to Terraform v0.15
description: Upgrading to Terraform v0.15
---

# Upgrading to Terraform v0.15

Terraform v0.15 is a major release and so it includes some small changes in
behavior that you may need to consider when upgrading. This guide is intended
to help with that process.

The goal of this guide is to cover the most common upgrade concerns and
issues that would benefit from more explanation and background. The exhaustive
list of changes will always be
[the Terraform Changelog](https://github.com/hashicorp/terraform/blob/v0.15/CHANGELOG.md).
After reviewing this guide, we recommend reviewing the Changelog to check for
specific notes about less-commonly-used features.

This guide is also not intended as an overview of the new features in
Terraform v0.15. This release includes other enhancements that don't need any
special attention during upgrade, but those are described in the changelog and
elsewhere in the Terraform documentation.

This guide focuses on changes from v0.14 to v0.15. Terraform supports upgrade
tools and features only for one major release upgrade at a time, so if you are
currently using a version of Terraform prior to v0.14 please upgrade through
the latest minor releases of all of the intermediate versions first, reviewing
the previous upgrade guides for any considerations that may be relevant to you.

Unlike the previous few Terraform major releases, v0.15's upgrade concerns are
largely conclusions of deprecation cycles left over from previous releases,
many of which already had deprecation warnings in v0.14. If you previously
responded to those while using Terraform v0.14 then you hopefully won't need
to make any special changes to upgrade, although we still recommend reviewing
the content below to confirm, particularly if you see new errors or unexpected
behavior after upgrading from Terraform v0.14.

-> If you run into any problems during upgrading that are not addressed by the
information in this guide, please feel free to start a topic in
[The Terraform community forum](https://discuss.hashicorp.com/c/terraform-core),
describing the problem you've encountered in enough detail that other readers
may be able to reproduce it and offer advice.

Upgrade guide sections:

* [Sensitive Output Values](#sensitive-output-values)
* [Legacy Configuration Language Features](#legacy-configuration-language-features)
* [Alternative (Aliased) Provider Configurations Within Modules](#alternative-provider-configurations-within-modules)
* [Commands Accepting a Configuration Directory Argument](#commands-accepting-a-configuration-directory-argument)
* [Microsoft Windows Terminal Support](#microsoft-windows-terminal-support)
* [Other Minor Command Line Behavior Changes](#other-minor-command-line-behavior-changes)
* [Azure Backend `arm_`-prefixed Arguments](#azure-backend-removed-arguments)

## Sensitive Output Values

Terraform v0.14 previously introduced the ability for Terraform to track and
propagate the "sensitivity" of values through expressions that include
references to sensitive input variables and output values. For example:

```hcl
variable "example" {
  type      = string
  sensitive = true
}

resource "example" "example" {
  # The following value is also treated as sensitive, because it's derived
  # from var.example.
  name = "foo-${var.example}"
}
```

As part of that feature, Terraform also began requiring you to mark an output
value as sensitive if its definition includes any sensitive values itself:

```hcl
output "example" {
  value = "foo-${var.example}"

  # Must mark this output value as sensitive, because it's derived from
  # var.example that is declared as sensitive.
  sensitive = true
}
```

Terraform v0.15 extends this mechanism to also work for values derived from
resource attributes that the provider has declared as being sensitive.
Provider developers will typically mark an attribute as sensitive if the
remote system has documented the corresponding field as being sensitive, such
as if the attribute always contains a password or a private key.

As a result of that, after upgrading to Terraform v0.15 you may find that
Terraform now reports some of your output values as invalid, if they were
derived from sensitive attributes without also being marked as sensitive:

```
╷
│ Error: Output refers to sensitive values
│ 
│   on sensitive-resource-attr.tf line 5:
│    5: output "private_key" {
│ 
│ Expressions used in outputs can only refer to sensitive values if the
│ sensitive attribute is true.
╵
```

If you were intentionally exporting a sensitive value, you can address the
error by adding an explicit declaration `sensitive = true` to the output
value declaration:

```hcl
output "private_key" {
  value     = tls_private_key.example.private_key_pem
  sensitive = true
}
```

With that addition, if this output value was a root module output value then
Terraform will hide its value in the `terraform plan` and `terraform apply`
output:

```
Changes to Outputs:
  + private_key = (sensitive value)
```

-> **Note:** The declaration of an output value as sensitive must be made
within the module that declares the output, so if you depend on a third-party
module that has a sensitive output value that is lacking this declaration then
you will need to wait for a new version of that module before you can upgrade
to Terraform v0.15.

The value is only hidden in the main UI, and so the sensitive value
will still be recorded in the state. If you declared this output value in order
to use it as part of integration with other software, you can still retrieve
the cleartext value using commands intended for machine rather than human
consumption, such as `terraform output -json` or `terraform output -raw`:

```shellsession
$ terraform output -raw private_key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoahsvJ1rIxTIOOmJZ7yErs5eOq/Kv9+5l3h0LbxW78K8//Kb
OMU3v8F3h8jp+AB/1zGr5UBYfnYp5ncJm/OTCXLFAHxGibEbRnf1m2A3o0hEaWsw
# (etc...)
```

If you consider Terraform's treatment of a sensitive value to be too
conservative and you'd like to force Terraform to treat a sensitive value as
non-sensitive, you can use
[the `nonsensitive` function](/language/functions/nonsensitive) to
override Terraform's automatic detection:

```hcl
output "private_key" {
  # WARNING: Terraform will display this result as cleartext
  value = nonsensitive(tls_private_key.example.private_key_pem)
}
```

For more information on the various situations where sensitive values can
originate in Terraform, refer to the following sections:

* [Sensitive Input Variables](/language/values/variables#suppressing-values-in-cli-output)
* [Sensitive Resource Attributes](/language/expressions/references#sensitive-resource-attributes)
* [Sensitive Output Values](/language/values/outputs#sensitive)

-> **Note:** The new behavior described in this section was previously
available in Terraform v0.14 as the
[language experiment](/language/settings/#experimental-language-features)
`provider_sensitive_attrs`. That experiment has now concluded, so if you were
participating in that experiment then you'll now need to remove the experiment
opt-in from your module as part of upgrading to Terraform v0.15.

## Legacy Configuration Language Features

Terraform v0.12 introduced new syntax for a variety of existing Terraform
language features that were intended to make the language easier to read and
write and, in some cases, to better allow for future changes to the language.

Many of the old forms remained available but deprecated from v0.12 through to
v0.14, with these deprecations finally concluding in the v0.15 release. Those
who used the `terraform 0.12upgrade` command when upgrading from Terraform v0.11
to v0.12 will have had these updated automatically, but we've summarized the
changes below to help with any remaining legacy forms you might encounter while
upgrading to Terraform v0.15:

* The built-in functions `list` and `map` were replaced with first-class syntax
  `[ ... ]` and `{ ... }` in Terraform v0.12, and we've now removed the
  deprecated functions in order to resolve the ambiguity with the syntax used
  to declare list and map type constraints inside `variable` blocks.

  If you need to update a module which was using the `list` function, you
  can get the same result by replacing `list(...)` with `tolist([...])`.
  For example:

  ```diff
  - list("a", "b", "c")
  + tolist(["a", "b", "c"])
  ```

  If you need to update a module which was using the `map` function, you
  can get the same result by replacing `map(...)` with `tomap({...})`.
  For example:

  ```diff
  - map("a", 1, "b", 2)
  + tomap({ a = 1, b = 2 })
  ```

  The above examples include the type conversion functions `tolist` and
  `tomap` to ensure that the result will always be of the same type as
  before. However, in most situations those explicit type conversions won't
  be necessary because Terraform can infer the necessary type conversions
  automatically from context. In those cases, you can just use the
  `[ ... ]` or `{ ... }` syntax directly, without a conversion function.

* In `variable` declaration blocks, the `type` argument previously accepted
  v0.11-style type constraints given as quoted strings. This legacy syntax
  is removed in Terraform v0.15.

  To update an old-style type constraint to the modern syntax, start by
  removing the quotes so that the argument is a bare keyword rather than
  a string:

  ```hcl
  variable "example" {
    type = string
  }
  ```

  Additionally, if the previous type constraint was either `"list"` or
  `"map`", add a type argument to specify the element type of the collection.
  Terraform v0.11 typically supported only collections of strings, so in
  most cases you can set the element type to `string`:

  ```hcl
  variable "example" {
    type = list(string)
  }

  variable "example" {
    type = map(string)
  }
  ```

* In `lifecycle` blocks nested inside `resource` blocks, Terraform previously
  supported a legacy value `["*"]` for the `ignore_changes` argument, which
  is removed in Terraform v0.15.

  Instead, use the `all` keyword to indicate that you wish to ignore changes
  to all of the resource arguments:

  ```hcl
    lifecycle {
      ignore_changes = all
    }
  ```

* Finally, Terraform v0.11 and earlier required all non-constant expressions
  to be written using string interpolation syntax, even if the result was
  not a string. Terraform v0.12 introduced a less confusing syntax where
  arguments can accept any sort of expression without any special wrapping,
  and so the interpolation-style syntax has been redundant and deprecated
  in recent Terraform versions.

  For this particular change we have not made the older syntax invalid, but
  we do still recommend updating interpolation-only expressions to bare
  expressions to improve readability:

  ```diff
  - example = "${var.foo}"
  + example = var.foo
  ```

  This only applies to arguments where the value is a single expression without
  any string concatenation. You must continue to use the `${ ... }` syntax for
  situations where you are combining string values together into a larger
  string.

  The `terraform fmt` command can detect and repair simple examples of the
  legacy interpolation-only syntax, and so we'd recommend running
  `terraform fmt` on your modules once you've addressed any of the other
  situations above that could block configuration parsing in order to update
  your configurations to the typical Terraform language style conventions.

## Alternative Provider Configurations Within Modules

Terraform's provider configuration scheme includes the idea of a "default"
(unaliased) provider configuration along with zero or more alternative
(aliased) provider configurations.

The `required_providers` block now has a new field for providers to indicate
aliased configuration names, replacing the need for an empty "proxy
configuration block" as a placeholder. In order to declare
[configuration aliases](/language/modules/develop/providers#provider-aliases-within-modules),
add the desired names to the `configuration_aliases` argument for the provider
requirements.

```hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 2.7.0"
      configuration_aliases = [ aws.alternate ]
    }
  }
}
```

Warnings will be emitted now where empty configuration blocks are present but
no longer required, though they continue to work unchanged in the 0.15 release.
There are a few cases where existing configurations may return new errors:

* The `providers` map in a module call cannot override a provider configured
  within the module. This is not a supported configuration, but was previously
  missed in validation and now returns an error.

* A provider alias within a module that has no configuration _requires_ a
  provider configuration be supplied in the module `providers` map.

* All entries in the `providers` map in a module call must correspond to a
  provider name within the module. Passing in a configuration to an undeclared
  provider is now an error.

## Commands Accepting a Configuration Directory Argument

A subset of Terraform's CLI commands have historically accepted a final
positional argument to specify which directory contains the root module of
the configuration, overriding the default behavior of expecting to find it
in the current working directory.

However, the design of that argument was flawed in a number of ways due to
it being handled at the wrong level of abstraction: it only changed where
Terraform looks for configuration and not any of the other files that Terraform
might search for, and that could therefore violate assumptions that Terraform
configurations might make about the locations of different files, leading
to confusing error messages. It was also not possible to support this usage
pattern across all commands due to those commands using positional arguments
in other ways.

To address these design flaws, Terraform v0.14 introduced a new global option
`-chdir` which you can use before the subcommand name, causing Terraform to
run the subcommand as if the given directory had been the current working
directory:

```shellsession
$ terraform -chdir=example init
```

This command causes the Terraform process to actually change its current
working directory to the given directory before launching the subcommand, and
so now any relative paths accessed by the subcommand will be treated as
relative to that directory, including (but not limited to) the following key
directory conventions:

* As with the positional arguments that `-chdir` replaces, Terraform will look
  for the root module's `.tf` and `.tf.json` files in the given directory.

* The `.tfvars` and `.tfvars.json` files that Terraform automatically searches
  for, and any relative paths given in `-var-file` options, will be searched
  in the given directory.

* The `.terraform` directory which Terraform creates to retain the working
  directory internal state will appear in the given directory, rather than
  the current working directory.

After treating the v0.14 releases as a migration period for this new behavior,
Terraform CLI v0.15 no longer accepts configuration directories on any
command except `terraform fmt`. (`terraform fmt` is special compared to the
others because it primarily deals with configuration files in isolation,
rather than modules or configurations as a whole.)

If you built automation which previously relied on overriding the configuration
directory alone, you will need to transition to using the `-chdir` command line
option before upgrading to Terraform v0.15.

Since the `-chdir` argument behavior is more comprehensive than the positional
arguments it has replaced, you may need to make some further changes in the
event that your automation was relying on the limitations of the old mechanism:

* If your system depends on the `.terraform` directory being created in the
  _real_ current working directory while using a root module defined elsewhere,
  you can use the `TF_DATA_DIR` environment variable to specify the absolute
  path where Terraform should store its working directory internal state:

  ```bash
  TF_DATA_DIR="$PWD/.terraform"
  ```

* If your system uses `.tfvars` or `.tfvars.json` files either implicitly found
  or explicitly selected in the current working directory, you must either
  move those variables files into the root module directory or specify your
  files from elsewhere explicitly using the `-var-file` command line option:

  ```bash
  terraform plan -var-file="$PWD/example.tfvars"
  ```

As a special case for backward compatibility, Terraform ensures that the
language expression `path.cwd` will return the _original_ working directory,
before overriding with `-chdir`, so that existing configurations referring to
files in that directory can still work. If you want to refer to files in the
directory given in `-chdir` then you can use `path.root`, which returns the
directory containing the configuration's root module.

## Microsoft Windows Terminal Support

Until the first Windows 10 update, Microsoft Windows had a console window
implementation with an API incompatible with the virtual terminal approach
taken on all other platforms that Terraform supports.

Previous versions of Terraform accommodated this by using an API translation
layer which could convert a subset of typical virtual terminal sequences into
corresponding Windows Console API function calls, but as a result this has
prevented Terraform from using more complex terminal features such as progress
indicators that update in place, menu prompts, etc.

Over the course of several updates to Windows 10, Microsoft has introduced
virtual terminal support similar to other platforms and
[now recommends the virtual terminal approach for console application developers](https://docs.microsoft.com/en-us/windows/console/classic-vs-vt).
In response to that recommendation, Terraform v0.15 no longer includes the
terminal API translation layer and consequently it will, by default, produce
incorrectly-formatted output on Windows 8 and earlier, and on non-updated
original retail Windows 10 systems.

If you need to keep using Terraform on an older version of Windows, there are
two possible workarounds available in the v0.15.0 release:

* Run Terraform commands using the `-no-color` command line option to disable
  the terminal formatting sequences.

  This will cause the output to be unformatted plain text, but as a result
  will avoid the output being interspersed with uninterpreted terminal
  control sequences.

* Alternatively, you can use Terraform v0.15.0 in various third-party
  virtual terminal implementations for older Windows versions, including
  [ConEmu](https://conemu.github.io/), [Cmder](https://cmder.net/),
  and [mintty](https://mintty.github.io/).

Although we have no immediate plans to actively block running Terraform on
older versions of Windows, we will not be able to test future versions of
Terraform on those older versions and so later releases may contain
unintended regressions. We recommend planning an upgrade to a modern Windows
release on any system where you expect to continue using Terraform CLI.

## Other Minor Command Line Behavior Changes

Finally, Terraform v0.15 includes a small number of minor changes to the
details of some commands and command line arguments, as part of a general
cleanup of obsolete features and improved consistency:

* Interrupting Terraform commands with your operating system's interrupt
  signal (`SIGINT` on Unix systems) will now cause Terraform to exit with
  a non-successful exit code. Previously it would, in some cases, exit with
  a success code.

  This signal is typically sent to Terraform when you press
  Ctrl+C or similar interrupt keyboard shortcuts in an interactive terminal,
  but might also be used by automation in order to gracefully cancel a
  long-running Terraform operation.

* The `-lock` and `-lock-timeout` options are no longer available for the
  `terraform init` command. Locking applies to operations that can potentially
  change remote objects, to help ensure that two concurrent Terraform processes
  don't try to run conflicting operations, but `terraform init` does not
  interact with any providers in order to possibly effect such changes.

  These options didn't do anything in the `terraform init` command before,
  and so you can remove them from any automated calls with no change
  in behavior.

* The `-verify-plugins` and `-get-plugins` options to `terraform init` are
  no longer available. These have been non-functional since Terraform v0.13,
  with the introduction of the new Terraform Registry-based provider installer,
  because in practice there are very few operations Terraform can perform which
  both require a `terraform init` but can also run without valid provider
  plugins installed.

  If you were using these options in automated calls to `terraform init`,
  remove them from the command line for compatibility with Terraform v0.15.
  There is no longer an option to initialize without installing the
  required provider plugins.

* The `terraform destroy` command no longer accepts the option `-force`. This
  was a previous name for the option in earlier Terraform versions, but we've
  since adopted `-auto-approve` for consistency with the `terraform apply`
  command.

  If you are using `-force` in an automated call to `terraform destroy`,
  change to using `-auto-approve` instead.

## Azure Backend Removed Arguments

In an earlier release the `azure` backend changed to remove the `arm_` prefix
from a number of the configuration arguments:

| Old Name              | New Name          |
| --------------------- | ----------------- |
| `arm_client_id`       | `client_id`       |
| `arm_client_secret`   | `client_secret`   |
| `arm_subscription_id` | `subscription_id` |
| `arm_tenant_id`       | `tenant_id`       |

The old names were previously deprecated, but we've removed them altogether
in Terraform v0.15 in order to conclude that deprecation cycle.

If you have a backend configuration using the old names then you may see
errors like the following when upgrading to Terraform v0.15:

```
╷
│ Error: Invalid backend configuration argument
│
│ The backend configuration argument "arm_client_id" given on
│ the command line is not expected for the selected backend type.
╵
```

If you see errors like this, rename the arguments in your backend configuration
as shown in the table above and then run the following to re-initialize your
backend configuration:

```
terraform init -reconfigure
```

The `-reconfigure` argument instructs Terraform to just replace the old
configuration with the new configuration directly, rather than offering to
migrate the latest state snapshots from the old to the new configuration.
Migration would not be appropriate in this case because the old and new
configurations are equivalent and refer to the same remote objects.
