REPLACE_AT_BRINGUP Passes Terraform precondition
This article may contain affiliate links. Its content is not affected by advertising.
In short
A Terraform precondition only checks that image is non-empty, so a placeholder like REPLACE_AT_BRINGUP passes plan - the real failure waits until Cloud Run pulls the image at runtime.
A Terraform precondition only checks that image is non-empty, so a placeholder like REPLACE_AT_BRINGUP passes plan - the real failure waits until Cloud Run pulls the image at runtime.
Conclusion
A Cloud Run module’s precondition only checks var.image != "" - whether the variable is non-empty - so a placeholder image tag like REPLACE_AT_BRINGUP, left in place before a build, is not an empty string and sails straight through terraform plan. The real failure does not happen at plan time; it is deferred until a Cloud Run Job or revision actually tries to pull an image using that tag at runtime. This is read from a commit that rewrote a production Terraform code comment away from the false assumption that plan would catch a forgotten placeholder.
Symptom
The production Cloud Run definition infrastructure/terraform/envs/prod/main.tf originally carried this comment:
# infrastructure/terraform/envs/prod/main.tf:18-19 (before fix, sha: cc5385d2)
# A forgotten web image substitution (still REPLACE_AT_BRINGUP) is caught
# fail-fast at plan time by the cloud_run module's precondition (image != "") below.
This comment assumes that if someone runs apply while image still holds a pre-build placeholder, the precondition will stop it during plan. But a placeholder string like REPLACE_AT_BRINGUP left in the image variable is not an empty string.
Cause
The actual precondition that gets evaluated lives in infrastructure/terraform/modules/cloud_run/main.tf:
# infrastructure/terraform/modules/cloud_run/main.tf:395-398 (sha: bf9bc4d7, logic unchanged by the fix)
precondition {
condition = !var.enabled || var.image != ""
error_message = "image is required when enabled = true (a built and pushed web image, apps/web/Dockerfile)."
}
This expression checks only one thing: when enabled is true, is image non-empty? The string REPLACE_AT_BRINGUP has non-zero length, so the condition evaluates to true and the precondition passes. A precondition can only check what is statically known at plan time - a variable’s type, or whether it’s empty - not whether the string actually points to an image that exists, which would require a request to the cloud. plan is local plan generation; no image pull against Cloud Run happens at this point.
In other words, the only point where a forgotten substitution can actually be caught is at apply time, when a Cloud Run Job or revision tries to pull an image using that tag. The precondition evaluated during plan cannot catch it, by design.
The fix
The commit (bf9bc4d7, 2026-10-09, author: cometa-kaito) did not change any Terraform logic - it only changed the comment in envs/prod/main.tf. The precondition expression itself is byte-for-byte identical before and after.
# infrastructure/terraform/envs/prod/main.tf:18-19 (after fix, sha: bf9bc4d7)
# A forgotten web image substitution (still REPLACE_AT_BRINGUP) is NOT caught by
# the cloud_run module's precondition below (image != "" is a non-empty check only;
# plan time does not verify the image actually exists). The real failure happens
# when a Job or revision tries to pull the image using that tag.
The same commit also corrected a matching passage elsewhere in the same file (around line 167). What changed is the understanding of what the precondition actually guarantees - this commit adds no new check that would catch a forgotten substitution at plan time.
Preventing a repeat
Nothing in this commit’s record adds a mechanical check or CI guard that would catch a forgotten substitution at plan time. What can be written here is only the structural reason the misconception survived as a comment for as long as it did.
The name precondition, together with the Japanese error message’s specific wording (“image is required”), reads as reassuring - it invites the reader to assume image correctness is being verified here. But the condition itself is nothing more than a minimal != "" check; neither the variable being named image nor the error message being specific says anything about how much the check actually covers. Reading a precondition block means reading what the condition expression itself compares, directly - not inferring the check’s scope from the surrounding comment or variable name.
Frequently asked questions
Q1What exactly does the precondition check?
The cloud_run module precondition is one line: !var.enabled || var.image != "". When enabled is true, it only checks that var.image is not an empty string. It cannot tell a real image URL from a placeholder typed before the build - both are simply non-empty.
Q2What exactly is a placeholder image tag?
It is a stand-in value left in var.image before the web Dockerfile is built and pushed, such as the string REPLACE_AT_BRINGUP. Since it is not an empty string, it still passes the preconditions non-empty check.
Q3Did this commit change the Terraform logic?
No. The preconditions logic itself was not changed. What was fixed was a code comment in envs/prod/main.tf: it used to claim the precondition would catch a forgotten placeholder, and now says plan cannot catch it - only the runtime image pull can fail.
Q4How was this noticed in review?
Per the commit record, the plan-time behavior was flagged in review and then fixed. A precondition is evaluated during plan, before any cloud image pull happens. A non-empty check and a check that the image truly exists are different things - that distinction is what the comment fix records.
Environment verified
- Terraform required_version ">= 1.9.0, < 2.0.0" / google, google-beta provider "~> 6.0"
- Code comment fixed 2026-10-09 (commit bf9bc4d7)
What this article is based on
- Terraform file lines 18-19commit cc5385d
- Terraform file lines 18-19commit bf9bc4d
- Terraform file lines 395-398commit bf9bc4d
Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.