Terraform Plan Dry Runs¶
3AM Quick Guide¶
- Use this only for IaC merge requests in repositories that already support
the
BF_PIPELINE_TYPE=dry-runpath. - Get the required senior-engineer security review before exposing protected variables or runners to the merge request pipeline.
- Temporarily enable MR access to protected variables and runners, and temporarily protect the MR source branch.
- Trigger a new merge request pipeline with a commit message containing
[dry-run]. - In the pipeline, confirm the run is actually dry-run mode, review the Terraform plan artifacts, and make sure no deploy or apply jobs appear.
- Remove the temporary protected access as soon as the review is complete. If deploy or apply jobs appear, stop immediately and disable access.
Purpose And When To Use It¶
This runbook explains how to validate infrastructure-as-code changes in a merge request by running Terraform plan through the merge request dry-run pipeline path before merge, without publishing releases, pushing Docker images, or running deploy or apply jobs.
The shared trigger lives in bf-dev CI templates. Downstream repositories may
implement different Terraform plan jobs, environments, regions, and evidence
artifacts while still using the same BF_PIPELINE_TYPE=dry-run mechanism.
Use this workflow when a merge request changes Terraform or other IaC that needs plan-level validation, and the plan requires protected variables or protected runners that are not normally available to merge request pipelines.
This workflow exists so reviewers can inspect the Terraform plan for the MR
before merge, without taking on the risks of running terraform plan locally
and without needing to merge first just to see the plan output.
Do not use this workflow for non-IaC changes, release pipelines, or any change that has not first been reviewed for CI and secret-handling safety.
Prerequisites And Permissions¶
Before using this workflow:
- A senior engineer must review the merge request and confirm the pipeline or CI changes do not expose protected variables or runners unsafely.
- That same senior engineer must temporarily change the repository CI/CD settings and branch protection while the dry run is being performed.
- The target repository must actually implement the dry-run path. In the
current BetterFleet setup, the shared trigger lives in
ci-templates/Orchestration-Workflow.gitlab-ci.yml, while each repository owns its own dry-run-safe plan job rules and evidence outputs. - The dry-run pipeline is only triggered for merge request pipelines whose
commit message contains
[dry-run].
In repositories that support this workflow:
BF_PIPELINE_TYPEbecomesdry-runDRY_RUNbecomestrue- any release metadata jobs needed by Terraform inputs should emit the expected variables without publishing a release
- any build prerequisites should exit through their dry-run-safe path without building or pushing deployable artifacts
- Terraform plan jobs should run or become available according to the target repository's established workflow
- Terraform deploy and apply jobs should be blocked from appearing in the pipeline
Normal Procedure¶
- Confirm the repository actually supports the dry-run path.
For the BetterFleet mechanism, the shared trigger is defined in
bf-dev/ci-templates/Orchestration-Workflow.gitlab-ci.yml. The target repository must also have its own dry-run-safe release, build, and Terraform plan job rules. - Complete the security review before exposing protected variables to the MR pipeline.
- Temporarily enable protected access in GitLab for the target repository.
Enable
Allow merge request pipelines to access protected variables and runners. - Temporarily add the MR source branch to the protected branches list.
- Trigger the dry-run pipeline with a commit message containing
[dry-run].
- Open the MR pipeline and confirm the dry-run trigger conditions are in
effect.
Confirm that the pipeline was created from the merge request after the
[dry-run]commit, and confirm through job context, logs, or variable output thatBF_PIPELINE_TYPEresolved todry-runandDRY_RUNresolved totruewhere the target repository exposes those values. - Wait for the dry-run prerequisites to complete. The exact prerequisite jobs vary by repository, but any release metadata and build-related steps should succeed through their dry-run-safe branches rather than publishing a release or pushing deployable artifacts.
- Review the Terraform plan jobs and artifacts.
The exact plan jobs, environments, regions, and artifacts vary by
repository. Review the target repository's configured plan jobs and use the
relevant evidence artifacts for that repository, such as the GitLab
Terraform report,
plan.json,plan.cache,outputs.json, or equivalent outputs. - Confirm no deploy or apply jobs are available in the pipeline before you treat the run as safe for review.
- Remove the temporary protected access as soon as the dry-run pipeline is no longer needed.
Reference Screenshots¶
These screenshots show the GitLab settings used during steps 3 and 4 of the normal procedure.
| Screenshot | What It Shows | File |
|---|---|---|
![]() |
The GitLab CI/CD settings area where Allow merge request pipelines to access protected variables and runners is enabled temporarily. |
terraform-plan-dry-run/cicd-variables.png |
![]() |
The branch protection page where the MR source branch is temporarily added to the protected branches list. | terraform-plan-dry-run/branch-protection.png |
Decision Points And Exceptions¶
- If the repository does not inherit the shared BFDev dry-run trigger or does not have downstream dry-run-safe Terraform plan jobs, stop and use a repository-specific process instead.
- If the merge request does not change Terraform or other IaC that needs plan validation, do not use this workflow.
- If the merge request changes CI definitions, pipeline scripts, Terraform job wiring, or secrets usage, treat that as a higher-risk run and require an explicit security review before enabling protected access.
- If the target repository already has an established safe way to expose MR plan output without temporarily enabling protected access, use that repository-specific workflow instead.
- If the
[dry-run]trigger commit is present but the pipeline does not behave like the dry-run path, or ifBF_PIPELINE_TYPEis notdry-runwhere that value is visible, stop and inspect the trigger conditions before trusting the result. - If any deploy or apply job appears in the pipeline, stop immediately, disable protected access, and treat the configuration as unsafe until reviewed.
- If the target repository runs plan jobs automatically once prerequisites finish, review all relevant jobs or record the subset you intentionally checked.
- If the target repository exposes manual plan jobs, run only the approved subset and record exactly which environments and regions were reviewed.
Validation And Evidence¶
Treat a dry-run review as complete only when you can point to evidence for each of these checks:
- The pipeline was triggered from the merge request by a commit containing
[dry-run], and the observed job behavior matches the dry-run path. BF_PIPELINE_TYPE=dry-runandDRY_RUN=truewere confirmed where the target repository exposes those values in job context, logs, or variable output.- The prerequisite jobs completed through their dry-run-safe branches rather than publishing a release or pushing Docker images.
- The relevant Terraform plan jobs completed and produced reviewable artifacts or GitLab Terraform reports.
- The reviewed plan output corresponds to the IaC changes in the merge request and was available before merging the branch.
- No deploy or apply jobs were available in the pipeline.
- The merge request or review note records which plan jobs were checked and any follow-up action or decision that came from the output.
- The temporary protected access changes were removed after the review.
Rollback And Recovery¶
After the dry-run pipeline completes:
- disable
Allow merge request pipelines to access protected variables and runners - remove the MR source branch from the protected branches list
- leave a short note in the MR if the dry-run output informed a decision
If the dry-run pipeline exposes unexpected jobs or behavior:
- cancel the pipeline if it is still running
- disable protected MR access immediately
- remove the MR source branch from protected branches
- capture the pipeline link and the unexpected job names in the merge request
- stop using this workflow until the CI rules are reviewed and corrected
Links To Service-Specific Details¶
- Shared trigger and workflow context: CI and Release Integration
- Current shared trigger implementation:
ci-templates/Orchestration-Workflow.gitlab-ci.yml - Current dry-run release metadata implementation:
ci-templates/Semantic-Release.gitlab-ci.yml - Target repository Terraform-plan implementation:
the target repository's
.gitlab-ci.ymland included CI templates - Target repository runbooks and service docs: use the target repository's own docs for plan job names, environments, regions, approval rules, and evidence expectations

