I was looking into the below GitHub Actions workflow, and was surprised to see that terraform apply terraform.tfplan
was working, despite a full initialization wasn’t performed (terraform init -backend=false
).
This made me dive deeper into the terraform init
and terraform plan
commands.
The -backend=false
option is documented as follows:
Disable backend or Terraform Cloud initialization for this
configuration and use what was previously initialized instead.
What does this really mean? Doesn’t this assume that terraform init
was already run on the configuration? Why would I run it again with -backend=false
then?
Looking into the generated plan, I observed that it includes, among other things, the .terraform.lock.hcl
lockfile, and the S3 backend configuration in the (binary) tfplan file:
➜ tfplan unzip terraform.tfplan
Archive: terraform.tfplan
inflating: tfplan
inflating: tfstate
inflating: tfstate-prev
inflating: tfconfig/m-/providers.tf
inflating: tfconfig/m-/main.tf
inflating: tfconfig/modules.json
inflating: .terraform.lock.hcl
In the terraform plan
documentation, it’s not stated that the generated plan includes backend configuration and the lockfile, so that terraform apply
can be run on it without performing a full initialization (i.e. only downloading modules)? Should I avoid depending on this?
https://developer.hashicorp.com/terraform/cli/commands/plan
GitHub Actions workflow:
....
terraform-plan:
name: terraform-plan
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: terraform
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: eu-west-1
role-to-assume: arn:aws:iam::729187411107:role/github-oidc-role
role-duration-seconds: 3600
role-session-name: github-${{ github.sha }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.7
- name: Terraform Init
run: |
terraform init
-backend-config="bucket=terraform-approve-before-apply-tfstate"
-backend-config="key=terraform.tfstate"
-backend-config="region=eu-west-1"
- name: Terraform Plan
run: terraform plan -out=terraform.tfplan
- name: Upload Terraform Plan
uses: actions/upload-artifact@v4
with:
name: tfplan
path: |
terraform/terraform.tfplan
terraform/.terraform.lock.hcl
if-no-files-found: error
needs: terraform-validate
terraform-apply:
name: terraform-apply
runs-on: ubuntu-latest
timeout-minutes: 30
environment: deploy
defaults:
run:
working-directory: terraform
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: eu-west-1
role-to-assume: arn:aws:iam::729187411107:role/github-oidc-role
role-duration-seconds: 3600
role-session-name: github-${{ github.sha }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.7
- name: Terraform Init
run: terraform init -backend=false
- name: Download Terraform Plan
uses: actions/download-artifact@v4
with:
name: tfplan
path: terraform
- name: Terraform Apply
run: terraform apply terraform.tfplan
needs: terraform-plan
2
Answers
Terraform init does few things:
And if you run this command
-backend=false
it skips backend initialization using one available.Of course if there is .terraform.lock.hcl it will reuse this instead of creating a new one.
But still some other steps are necessary to run further commands.
"Working directory initialization" as performed by the
terraform init
command largely consists of creating various files and directories under the.terraform
directory that Terraform uses either to avoid repeating work that was already done byinit
, or to help ensure that the execution environment remains consistent between commands.A saved plan file does include a subset of the information that
terraform init
normally establishes. The primary purpose of duplicating that information is to help avoid a mistake of accidentally applying a saved plan in a different context than where it was created. For example, it aims to help avoid someone accidentally applying a plan to a different workspace than it was intended for, or with different provider versions than it was created with.The information in the saved plan file is not necessarily complete. It contains information necessary for Terraform to avoid applying the plan in a different context, and it may include some additional information beyond what’s strictly required just because implementation details lead to that being true, but a saved plan file is not designed to be a self-contained representation of everything needed to apply the plan.
The details of what is strictly required to apply the saved plan have varied over time as Terraform’s implementation details have shifted, and so I would recommend against designing any workflow automation that makes assumptions about information stored in the plan file. Instead, you should think of the information in the plan file as part of a set of guardrails to help avoid mistakes.
To directly answer your question, then: if you find that you are able to run
terraform apply PLANFILE
without fully completing the working directory initialization equivalent to what you performed before creating the plan then you are relying on an implementation detail that might not hold in other versions of Terraform. The intention is that the saved plan file format is private to Terraform and subject to change at any time, and that you will apply the plan in a working directory initialized equivalently to the one that created the plan file.Although it does not directly address the specific point you were asking about here, I suggest referring to Plan and Apply on Different Machines — a part of the tutorial Running Terraform in Automation — which describes the recommended strategy for applying a saved plan in a different working directory (typically, on a different computer) than where it was created, and some constraints and assumptions Terraform makes that you must honor for that process to be generally reliable.