Terraform 1.17.0 landed on 21 October 2026 with -minimal-refresh, the flag that closes issue #35290 (terraform plan -light, 207 reactions) after two years. The short version: it refreshes only the resource instances whose plan already shows a change, so a drift job running terraform plan -minimal-refresh -detailed-exitcode returns 0 on infrastructure that has moved underneath you. Use it pre-merge, keep the nightly drift run on a full refresh, and spend five minutes on the other eleven items below before you bump the version in CI. The last Terraform release that cost this site an afternoon was the 1.16.3 download served as binary/octet-stream, and that one at least failed loudly.
The tips
- Do not put
-minimal-refreshin the job that detects drift. Terraform plans against stored state first and refreshes only the instances whose plan shows a change, so an externally modified resource with no config delta is never read back and-detailed-exitcodereturns 0 instead of 2. The v1.17 plan reference states the cause outright: "this option causes Terraform to ignore external changes to resources that have no planned changes." A test on 1.17.0-rc2 with twolocal_fileresources reproduced it: a hand-editedout/flags.jsonprintedNo changes. Your infrastructure matches the configuration.and exited 0, while a plain plan refreshed both and exited 2. A pipeline that reports success because it never looked has precedent in CI, see the Actions runner that exits 0 with .runner deleted.
# fast pre-merge plan
terraform plan -minimal-refresh -detailed-exitcode # 0 = no changes it bothered to look for
# nightly drift job: leave it alone
terraform plan -refresh-only -detailed-exitcode # 2 = something moved under you
- Audit your
applyinvocations, not onlyplan. The apply command reference notes that without a saved plan file, apply "supports all planning modes and planning options available forterraform plan". Any wrapper that templates a singleTF_ARGSstring into both commands hands-minimal-refreshto apply, where a stale read stops being a reporting problem and starts writing. Grep your Atlantis config, Terragrunt hooks and Makefiles for the variable, not for the flag. - Expect the speed win to vanish on provider upgrade day. The plan docs carve out one exception: "Terraform refreshes a resource regardless of the plan result if the resource schema version changes." A provider major bump that moves schema versions re-reads every instance, so a plan-duration SLO or a tight pipeline timeout built on the fast path will page someone at the worst moment.
- Resolve the flag conflicts before rollout. On 1.17.0-rc2,
-minimal-refreshcombined with-refresh=falseor-refresh-onlyerrors out, andterraform refreshrejects the flag entirely. Wrappers already carrying-refresh=falsefail at argument parsing, which is the outcome you want: the failure in tip 1 is the quiet one. - A variable used in
required_providersmust be declaredconst = true. PR #39153 made variables and locals valid inside provider requirements, and the variable block reference sets the constraint:const"allows the variable to be used when Terraform loads configuration, such as during a Terraform init operation," and such a variable "can only contain a known, constant value. It cannot depend on dynamic results from a plan operation." The same rule covers modulesourceandversion.
variable "provider_host" {
type = string
const = true
}
terraform {
required_providers {
aws = {
source = "${var.provider_host}/hashicorp/aws"
version = "~> 6.0"
}
}
}
- Move the registry host into a variable and leave the version literal.
constvariables cannot be ephemeral, per the #39362 implementation, and dependency bots parse HCL rather than evaluate it. lucasew made that argument on the predecessor PR #38405: static versions are "useful for stuff like Renovate to work well." A per-environment version expression also turns one.terraform.lock.hclinto a cross-environment argument, since-lockfile=readonlyconflicts with-upgrade. Variable-ise the host for an airgapped mirror and keepversionas a string Renovate can bump. - Grep for
log(andpow(before you upgrade. Both now raise an error rather than returning infinity: PR #39304 addedfmt.Errorf("result is not a number"), #38912 stopped the outright panic on NaN, and a test case expecting-Infwas deleted. Config computing a logarithm over a count that can reach zero used to emit a plausible-looking plan and now fails the run.
terraform console <<<'log(0, 10)' # 1.16: -Inf | 1.17: error, "result is not a number"
grep -rnE '\b(log|pow)\(' --include='*.tf' .
- Terraform 1.17 is built with Go 1.26.10 and your providers are not. The 1.17.0 release notes tie that build to CVE-2026-111943: the toolchain no longer trusts the compromised eMudhra intermediates. Each provider is a separate binary compiled by its maintainer with its own Go version, so
terraform initcan start failing against an internal registry while a provider keeps reaching the same internal API, or the reverse after the next provider release. The block is keyed on SPKI hashes compiled intocrypto/x509, so adding the CA to the system trust store changes nothing, which is why the error names no CA at all.
TF_LOG=trace terraform init 2>&1 | grep -i 'x509\|certificate'
go version -m .terraform/providers/*/*/*/*/terraform-provider-* | grep -E '^\s+mod|go1\.'
On Brotli patch day last week I probed the handshake instead of checking the inventory, on an end-of-life Node 20 host whose release number implied nothing useful and whose decoder returned errno=-9 for the unpatched path. That write-up is the php2shell and Node Brotli post, and the habit transfers directly: run the command, do not trust the version string.
- Budget for new warnings on applies that use ephemeral resources. PR #38989 fixed a dropped-diagnostics bug where the renewal path called
r.renewDiags.Append(diags)without assigning the result, discarding every warning and error raised while renewing an ephemeral resource instance. From 1.17 those surface when the instance closes, and a renewal error can mark an instance expired. Fresh noise on a long apply holding short-lived credentials means renewal was already failing and nobody was being told. - Delete the duplicated
-var-filefrom yourterraform showstep. Since 1.15 added-varto every command,showexpected variables to be supplied again when reading a saved plan. #39362 makes it read the values stored in the plan "regardless of any current variable values or defaults," so policy and cost-estimation stages that re-mount a tfvars file purely to render JSON can drop it.
terraform plan -out=tfplan
terraform show -json tfplan > plan.json # 1.17: no -var-file needed here
- Terraform Policy is GA, and
-policiesnow insists on a directory. PR #38970 removed the experimental-build and-allow-experimental-featuresrequirement for the-policiesflag onplan,applyandquery. Pointing it at a single.tfpolicy.hclfile used to pass validation and fail later inside the policy engine; #39238 makes it fail fast with an "Invalid policy path" error naming the path and exit 1. Keep the local loop honest withtfpolicy validate --policies=./policiesandtfpolicy test --policies=./policies. - Stop regexing init output, and bump terraform-json.
terraform version -jsonnow emits aformat_versionfield, tested with the value"1.0"in the terraform-json commit that addedFormatVersion, and strict Go decoders that reject unknown fields break on it. PR #38918 separately enriches init and state-migrate messages with provider versions and reshapes the JSONreusing_previous_version_infopayload, so a CI step scraping init text for a version string is now scraping a different sentence.
Wrap-up
Decide per job whether that run is allowed to be blind. -minimal-refresh belongs in the pre-merge plan where speed is the product, and the pipeline answering "has anything changed in production?" keeps paying for the full refresh, otherwise the 1.17 upgrade converts your drift detector into a machine that agrees with you. The rest of the list is grep work you can finish in one sitting: log(, pow(, -var-file on show, any .tfpolicy.hcl path passed to -policies, and the Go version baked into each provider binary.
Comments
Be the first to comment.