- Copy the system pipeline config you want to extend.
- Create your own pipeline in the project that should own it.
- Add the policy check step between plan and approval.
- Make it the default at the scope you want it enforced.
How a policy check stops a run
Steps run in order, and a step that exits nonzero fails the run. Put the check afterterraform:plan and before approval, and a violation stops the run before anyone can approve it — so nothing reaches terraform:apply.
The policy step is a custom step: it checks out your Terraform code and runs whatever commands you give it on an EC2 runner in your own cloud account. Your code, variables, and policy rules never leave your account.
Before you start
- The ID of the project that should own the pipeline — run
ravion project list. - A policy tool that exits nonzero on violations. Any CLI works, because the step is plain shell.
- The Ravion CLI authenticated against your organization.
Step 1: Copy the system pipeline config
List your pipelines and find Terraform Change Pipeline, the system pipeline with the given IDtf-change-pipeline. It appears in every organization, and its pipeline ID starts with pipe_:
Step 2: Create your own pipeline
Create an empty pipeline in the project that should own it, then note the returned pipeline ID:Step 3: Add the policy check step
Insert the step betweenplan and approve in change-pipeline.yaml. This example runs Checkov against the checked-out Terraform code, but any command works — swap in tflint, conftest, an OPA bundle, or your own script:
pipeline.input.* values means the step checks out the same commit and runs in the same account and region as the plan. See Templating for the available expressions and step types for every custom step field.
Three things worth knowing:
- Pin the tool version. Installing the latest release on every run lets a new release change your policy results, or a registry outage block stack changes, with no config change on your side.
-
Skip the check when nothing changed.
if: << steps.plan.output.has_changes >>keeps no-op runs fast. -
Gate on the plan’s blast radius. The plan step publishes change counts, so you can require a second approval only when resources would be destroyed:
To inspect the plan itself rather than the source, your commands must fetch and decode it:
plan_file_uri is an S3 URI for a binary plan file, so the step needs the same IaC tool and version, an initialized working directory, and read access to that bucket — grant it with infrastructure.permissions.attach. Checking the checked-out configuration needs none of that, so start there unless your rules depend on the diff.Step 4: Make your pipeline the default
Stacks resolve their pipelines from default values whose definition given IDs arechange_pipeline_id and destroy_pipeline_id. Find the definition ID:
A module definition can pin an explicit pipeline through
stack.pipelines.change.pipeline_id or
stack.pipelines.destroy.pipeline_id, which wins over the resolved default. The standard library
modules set these to << defaults.change_pipeline_id >> and << defaults.destroy_pipeline_id >>,
so defaults apply — but a definition that sets a literal pipeline ID ignores them. See the module
definition schema.Cover destroys too
The change pipeline runs when a module is created or changed. Deleting a module runs the destroy pipeline instead, so policies that must also govern teardown — for example requiring an extra approver for production deletions — need the same treatment on a copy of Terraform Destroy Pipeline (given IDtf-destroy-pipeline), pointed at through destroy_pipeline_id.
The destroy config is the same shape, with plan_type: destroy on its plan step. Destroy runs always stop for approval, because Ravion only passes autoapprove to change runs.