What Is New in Terraform 1.15?
Terraform 1.15 ships a meaningful set of additions: native deprecation markers on variables and outputs, a new convert() function for explicit type coercion, support for variables and locals inside module source and version attributes, and Windows ARM64 builds. Backend validation now runs at init time, and S3 backends gain aws login authentication. Below is a summary by category.
| Category | Highlights | Count |
|---|---|---|
| New Features | Windows ARM64 builds, deprecated attribute on variables/outputs, convert() function, variables in module source/version, S3 aws login auth, backend validation in validate |
6 |
| Enhancements | Typed output blocks, PowerShell re-enabled for SSH provisioners, millisecond timestamps in init, skip dev-override deps, functions in test mock blocks, richer deprecation messages, sensitive path logging |
8 |
| Bug Fixes | JUnit XML diagnostics, refresh-only plan exit codes, ephemeral resource crash in show -json, init -json backend messages, state show exit code, cross-workspace apply guard, HTTP backend lock info, output diff detection |
13 |
| Deprecated / Upgrade Notes | S3 backend: AWS_USE_FIPS_ENDPOINT and AWS_USE_DUALSTACK_ENDPOINT now only accept true/false |
1 |
| Experimental (alpha only) | Deferred actions (-allow-deferral), terraform test cleanup, backend blocks in test run blocks, skip_cleanup |
4 |
Which New Features Ship in Terraform 1.15?
Windows ARM64 Support
Terraform now publishes official binaries for Windows ARM64, so developers running Snapdragon-based Windows machines get native performance without emulation layers. This is a build artifact change -- no HCL changes are needed.
Deprecation Markers on Variables and Outputs
You can attach a deprecated attribute to any variable or output block. When a caller passes a value to a deprecated variable, or when downstream config references a deprecated output, Terraform emits a warning. In practice, this is the cleanest way to signal API changes in shared modules without breaking callers immediately.
variable "old_param" {
type = string
deprecated = "Use new_param instead. This variable will be removed in a future release."
}
output "legacy_endpoint" {
value = aws_lb.main.dns_name
deprecated = "Reference module.lb.endpoint going forward."
}
The convert() Function
The new built-in convert(value, type) performs an explicit, inline type conversion. Before this, coercing a value to a specific type required indirect workarounds like tostring(), tonumber(), etc. Now you can express the target type as a full type expression -- including complex types -- directly in the call.
# Convert a string to a number explicitly
locals {
port = convert(var.raw_port, number)
}
# Convert to a typed object
locals {
record = convert(var.input, object({ name = string, count = number }))
}
Variables and Locals in Module source and version
Module source and version attributes now accept input variables and locals. This was a long-requested capability for teams that dynamically compose registries or pin versions based on workspace-level variables. Most commands that consume variable values have been updated to support this pattern.
variable "module_version" {
type = string
default = "2.1.0"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = var.module_version
}
Backend Block Validation in terraform validate
The validate command now inspects your backend block, checking that the backend type exists, all required attributes are present, and the backend's own validation logic passes. This surfaces misconfigured backends before init -- useful in CI pipelines where you want early failure on config errors.
S3 Backend: aws login Authentication
The S3 backend now supports authentication via aws login, the credential flow introduced in recent AWS CLI versions. This complements existing auth methods (env vars, profiles, instance roles) and is the right choice for short-lived credential workflows.
What Got Better -- Enhancements in 1.15
Typed output Blocks
output blocks now accept an explicit type constraint, mirroring what variable blocks have always supported. This lets module authors enforce the shape of their outputs and catch type mismatches at plan time rather than at consumption time.
output "instance_ids" {
value = aws_instance.web[*].id
type = list(string)
}
PowerShell Re-enabled for SSH Provisioners
The file and remote-exec SSH-based provisioners now support PowerShell again. This restores a capability that had been disabled, making Windows remote provisioning via SSH first-class again for teams that still use provisioners.
Millisecond Timestamps in init Logs
terraform init log timestamps now include millisecond precision. Minor QoL improvement, but meaningful when diagnosing slow provider installs in CI where sub-second timing matters.
Skip Dev-Override Dependencies During init
When using developer overrides (dev_overrides in CLI config), terraform init now skips installing providers declared in the override file. This means you no longer need to remove dev overrides to run init successfully -- it installs only the providers that are not overridden.
Functions in Test Mock Blocks
Terraform Test now allows function calls inside mock_provider blocks. This makes mock data generation much more expressive -- you can call uuid(), timestamp(), or string functions directly in mock definitions without external workarounds.
Richer Deprecation Warnings from Providers
When a provider marks a resource attribute or block as deprecated, that deprecation message is now surfaced directly in the Terraform warning output. Previously, users had to dig into provider docs to understand what was being deprecated. Now the context travels with the warning.
Sensitive Attribute Paths in JSON Logs
Structured JSON logs (list_start events) now include the specific attribute paths that are marked sensitive. This is useful for log parsers and auditing pipelines that need to know exactly which attributes were redacted.
Input Variable Validation for Stacks
Stacks now run input variable validation rules, consistent with how standard Terraform configs handle variable constraints. This closes a gap where stack-level inputs could bypass validation logic that module authors had defined.
What Bugs Were Fixed in Terraform 1.15?
The 1.15 bug fix list is notably broad. Here are the ones most likely to affect day-to-day work:
- Refresh-only plan exit codes: A refresh-only plan could return a non-zero exit code even when there were no changes to report. This caused false failures in CI pipelines checking exit codes.
- Cross-workspace apply guard: Terraform now raises an explicit error when you attempt to apply a plan file against a workspace that differs from the one the plan was generated for. Previously this could silently proceed.
terraform show -jsoncrash: Fixed a crash that occurred when a plan contained ephemeral resources withpreconditionorpostconditionblocks.terraform init -jsonoutput format: Backend configuration messages now emit proper JSON when-jsonis passed. Previously, some messages fell through as plain text, breaking JSON log consumers.state showexit code: The command now exits with code 1 when it cannot render the named resource state. Before this fix it could exit 0 silently on failure.replace_triggered_byinvalid reference: The lifecycle meta-argument now reports a proper error when given an attribute reference that does not exist in the target resource, instead of silently ignoring it.- Output diff detection: Fixed a bug where Terraform could fail to detect differences between two states that had differing output values. This affected backend migration scenarios.
- HTTP backend lock info: The HTTP backend now returns the conflicting lock info when a lock cannot be acquired, rather than returning the lock that failed. This gives operators the correct information to resolve lock contention.
- JUnit XML test diagnostics: File-level error diagnostics are now included in JUnit XML output for skipped test elements, so CI systems can detect validation failures that were previously invisible in test reports.
nilpointer crash ininit: Fixed a nil pointer dereference that could occur duringterraform initwhen the destination backend returned an error.terraform fmtfor.tfquery.hcl: The formatter now handles.tfquery.hclfiles, which were previously not recognized.- Cloud/registry network resilience: Terraform Cloud and registry discovery requests are more resilient to transient network errors, reducing spurious init failures.
validateJSON output: Fixed cases wherevalidatedid not return JSON for certain early diagnostics when-jsonwas passed.
What Should You Watch Out For When Upgrading to 1.15?
S3 Backend: FIPS and Dual-Stack Environment Variables
The AWS_USE_FIPS_ENDPOINT and AWS_USE_DUALSTACK_ENDPOINT environment variables for the S3 backend now only accept exactly true or false. The previous behavior treated any non-empty string as true. If you relied on setting these to values like 1, yes, or enabled, update them before upgrading.
Provider Installation Order in init
Provider installation was internally refactored in this release. The order of operations during init has changed, and two new log messages replace one existing message (initializing_provider_plugin_message). If you parse Terraform init logs programmatically, update your parsers accordingly. End-user behavior is otherwise unchanged.
What Experimental Features Are Available in Terraform 1.15 Alpha Builds?
These features are only available in alpha builds and are not part of stable 1.15 releases.
- Deferred actions: Pass
-allow-deferraltoterraform planto allowcountandfor_eachin module, resource, and data blocks to have unknown values at plan time. Providers can then react more flexibly to unknowns. This targets cases where full evaluation is not possible until apply. terraform test cleanup: A new command that automatically cleans up local state files left behind by failed test cleanup operations. Without this, failed test runs could leave state files in.terraform/requiring manual removal.- Backend blocks in test run blocks: Test authors can now specify a
backendblock inside arunblock in.tftest.hclfiles. This loads existing state from a remote backend instead of starting from empty state, enabling tests against long-lived infrastructure. skip_cleanupin test files: A newskip_cleanupattribute tellsterraform testto preserve state files produced by the affectedrunblocks. Those files are written to.terraform/and can then be cleaned up manually or viaterraform test cleanup.
FAQ
Can I use var.something in the source attribute of a module block in 1.15?
Yes, this is now supported. Module source and version attributes accept variables and locals. You can pass variable values from the command line or .tfvars files as you normally would. Most Terraform commands have been updated to resolve these values before processing module sources.
What happens if I set AWS_USE_FIPS_ENDPOINT=1 with the S3 backend after upgrading to 1.15?
Terraform will no longer treat 1 as truthy. Only the exact string true enables FIPS endpoints now. Set the variable to true explicitly, or Terraform will behave as if FIPS is disabled. This aligns with the AWS SDK for Go behavior.
Does the new deprecated attribute on variables cause errors or just warnings?
It produces warnings, not errors. Callers can still pass values to deprecated variables and the plan will proceed. The intent is to give module consumers advance notice without breaking their workflows immediately. There is no automatic enforcement timeline built in -- that is up to the module author to manage through module versioning.
Is the convert() function safe to use with complex types like object and list?
Yes. The function accepts full type expressions, including list(string), map(number), object({ ... }), and so on. If the value cannot be converted to the target type, Terraform raises an error at plan time rather than silently producing a wrong value. In practice, it is most useful when consuming outputs from dynamic sources where the type is ambiguous.
Does the backend block validation in terraform validate require running terraform init first?
Yes, in most cases. Backend validation checks that the backend type is recognized and that its required attributes are present -- but for remote backends, the check still requires the provider plugins installed by init to be present. The improvement here is that validate now actively inspects the backend block instead of skipping it, which surfaces configuration errors earlier in local development workflows.