Sign in
Menu
Start free trial
Operational Rules

Operational Rules: your platform standards, applied at terraform init.

A lifecycle block cannot take a variable, so many teams fork a module to hold one. Operational Rules add it at download, and the fork can go.

One source line instead of a fork. The block arrives with the module.

module "s3_bucket" {
  source = "https://registry.compliance.tf/terraform-aws-modules/s3-bucket/aws?add_rules=lifecycle_prevent_destroy_data"
}

# added at terraform init
lifecycle { prevent_destroy = true }

See the exact change in the Playground, or read the docs to learn more.

The Playground needs no account.

Operational Rules for your Terraform modules

For
Your platform team, on Terraform or OpenTofu
Applied
When the module downloads, after the compliance controls
Set by
Your org admins, or one source line
1

Scope

1.1

Covers

  • terraform-aws-modules served from a framework host or your org endpoint. The rules change ordinary HCL at download, before Terraform reads the module, so what arrives in .terraform/modules/ is the upstream module with the change in it.
  • The 16 rules in the catalog (chapter 4): lifecycle blocks, provisioner removal, instance-type restriction, deletion protection, S3 abort-incomplete-uploads and allowed regions.
  • Terraform and OpenTofu alike, with the same source line.
1.2

Does not cover

  • Rules your team writes. Every rule in the catalog is written and maintained by compliance.tf.
  • Resources outside the modules compliance.tf serves. A rule changes the module it is served with, nothing else.
  • Problems no rule answers. The workarounds pages keep the native option for each module, which is sometimes the better answer anyway.
2

Why teams fork

Terraform does not let you pass prevent_destroy or ignore_changes into a module as a variable. It has been an open request since 2018, with hundreds of reactions across related issues. Teams that need one work around it:

WorkaroundWhat it costs
Fork every moduleA copy with lifecycle blocks added. Every upstream release means a rebase, a review to confirm the block still lands on the resource it was meant for, and a publish, repeated for each module.
Write wrapper modulesAnother layer of indirection that still needs lifecycle blocks inside.
Add PR review checklists"Did you add prevent_destroy?" Human memory does not scale.
OPA or Sentinel policiesCan flag a missing lifecycle block, but cannot add one to the code.
2.1

Known limits, module by module

No module in any registry can expose prevent_destroy or ignore_changes as an input. The docs collect the problems this causes in each terraform-aws-modules module: the native workaround first, the rule that answers it where one exists, and the diff the Playground shows. Free to read, free to try.

S3 bucketRDSEKSLambdaVPCDynamoDB tableAll modules with known limits

The full story: Stop paying the Terraform fork tax, and three forks replaced in From fork to one source line.

Two layers in one download. Your standards come second.

Compliance controls come from your framework. Operational Rules come from your platform team. Both are applied when the module is served, and you read the result as plain HCL.

  1. Upstream releaseterraform-aws-modules, unchanged
  2. Compliance controlsThe settings your framework requires
  3. Operational RulesYour Baseline, rulesets and per-request rules
  4. terraform initThe module arrives with both applied
3

Two layers, applied at download time

AspectCompliance controlsOperational Rules
What it enforcesRegulatory requirementsOrganizational standards
Driven byFrameworks: SOC 2, PCI DSS, HIPAA, NIST, ISO 27001 and othersPlatform team decisions
ExamplesEncryption at rest, access logging, public access blockingprevent_destroy, ignore_changes, instance restrictions
Who configurescompliance.tf, defined by the frameworkYour org admins
3.1

Controls first

The module is served with the controls for your framework first. Which controls apply, and the clauses each maps to, is on each framework page, for example SOC 2, and in the control catalog.

3.2

Rules second, merged additively

Operational Rules run after the controls. Where both change the same block, the changes merge additively.

3.3

No new tooling

Developers run terraform init with a compliance.tf module source. No new CLI or policy daemon. The module arrives with your standards already in the code.

16 rules. Every rule has a page with its diff, limits and failure modes.

Filter the catalog, or search by rule ID, resource or attribute, for example prevent_destroy or RDS.

16 of 16 shown

Each rule ID links to its public docs page. Every rule is applied at download time, to every module your organization enables it for.

4

Rule catalog

The rules in the catalog, with the change each one makes and the resources it applies to.

RuleWhat it doesApplies to
Prevent destroy datalifecycle_prevent_destroy_data

Adds a lifecycle block to stateful resources so accidental terraform destroy can't wipe production data.

lifecycle { prevent_destroy = true }S3, RDS instances and clusters, DynamoDB, EFS, ElastiCache
S3, RDS instances and clusters, DynamoDB, EFS, ElastiCache
Ignore tag changeslifecycle_ignore_tags

Adds a lifecycle block so external tag changes (AWS Config rules, cost allocation tools, FinOps automation) won't trigger plan diffs.

lifecycle { ignore_changes = [tags, tags_all] }All taggable resources
All taggable resources
Ignore autoscaling changeslifecycle_ignore_autoscaling_changes

Adds a lifecycle block so DynamoDB auto-scaling doesn't fight with Terraform on every plan.

lifecycle { ignore_changes = [read_capacity, write_capacity] }DynamoDB
DynamoDB
Ignore AMI changeslifecycle_ignore_ami_changes

Adds a lifecycle block so AMI updates from image pipelines don't cause unexpected instance replacements.

lifecycle { ignore_changes = [ami, image_id] }EC2 instances, launch templates
EC2 instances, launch templates
Prevent destroy encryptionlifecycle_prevent_destroy_encryption

Adds a lifecycle block to encryption keys and secrets. Losing a KMS key means losing access to everything it encrypted.

lifecycle { prevent_destroy = true }KMS, Secrets Manager
KMS, Secrets Manager
No provisionersprovisioner_remove_blocks

Removes all provisioner blocks from module resources. Provisioners run arbitrary commands outside Terraform's state model and break idempotency.

provisioner blocks removedAll resources
All resources
Restrict instance typesresource_restrict_instance_types

Denies GPU and specialty instance types (p3, p4, x1, x2, u-*) that can generate five- and six-figure monthly bills from a single terraform apply.

denies p3, p4, x1, x2, u-* typesEC2
EC2
Allowed regionsvariable_allowed_regions

Adds a validation block to the module's region variable so a region outside your organization's allowlist is rejected before anything is created.

validation { condition = contains(allowlist, var.region) }Modules with a region variable
Modules with a region variable
Ignore selected tag keyslifecycle_ignore_tag_keys

Adds a lifecycle block that ignores only the tag keys your organization names, so scanner or cost tags written outside Terraform stop showing up as drift.

lifecycle { ignore_changes = [tags["<key>"]] }S3 by default
S3 by default
Create before destroylifecycle_create_before_destroy

Adds a lifecycle block so a security group or ACM certificate is replaced create-first, and whatever is still attached to the old one keeps working through the swap.

lifecycle { create_before_destroy = true }Security groups, ACM certificates
Security groups, ACM certificates
Ignore deployed artifactslifecycle_ignore_deployed_artifacts

Adds a lifecycle block so the zip, image or task definition your pipeline deployed is not rolled back by the next terraform plan.

lifecycle { ignore_changes = [s3_key, image_uri, task_definition] }Lambda functions, ECS services
Lambda functions, ECS services
Ignore scaling changeslifecycle_ignore_scaling_changes

Adds a lifecycle block so the capacity an autoscaler sets on Auto Scaling groups stops showing as drift. terraform-aws-eks and the terraform-aws-ecs service module already ignore it; the rule covers node groups and services written outside them.

lifecycle { ignore_changes = [desired_capacity] }Auto Scaling groups; EKS node groups and ECS services outside terraform-aws-modules
Auto Scaling groups; EKS node groups and ECS services outside terraform-aws-modules
Set EKS node group desired sizeeks_node_group_set_desired_size

Opt-in. terraform-aws-eks ignores desired_size after create, so Terraform cannot resize a node group. This rule pushes desired, min and max size changes with the AWS CLI from terraform_data, so a min_size raise no longer fails.

terraform_data runs aws eks update-nodegroup-configEKS managed node groups (terraform-aws-eks)
EKS managed node groups (terraform-aws-eks)
Ignore secret rotationlifecycle_ignore_secret_rotation

Adds a lifecycle block so a database password that rotates outside Terraform stops proposing a reset on every plan.

lifecycle { ignore_changes = [password] }RDS instances, Aurora clusters
RDS instances, Aurora clusters
Set deletion protectionresource_set_deletion_protection

Turns on the provider's deletion protection where a module leaves it unset, so AWS refuses a stray delete from Terraform, the console or the API.

deletion_protection = trueRDS, Aurora, DynamoDB, load balancers
RDS, Aurora, DynamoDB, load balancers
Abort incomplete multipart uploadss3_lifecycle_abort_incomplete_uploads

Adds a validation to the module's lifecycle_rule variable requiring a rule that aborts incomplete multipart uploads, so parts that are billed but never listed stop accumulating.

validation { abort_incomplete_multipart_upload_days <= 7 }S3 buckets
S3 buckets

Set once for every team. Change one download when you need to.

Your org admins pick the rules once. Teams get them through the source line they already write, and the defaults are on record for every download.

Baseline
Every module from bobthecorp.compliance.tf
Ruleset
The modules that ask for it by name
Per download
Add or drop one rule in a single source line

Configured on the Operational Rules page of the compliance.tf console, or through the API.

5

Configure and override

5.1

Baseline

Org admins set a Baseline that applies to every module your organization downloads from its own host, from the Operational Rules page in the console or the API.

5.2

Named rulesets

A named ruleset applies to the modules that select it. On your organization's host, a module selects one in its source line. A ruleset adds to the Baseline; a source line cannot use it to subtract a rule.

source = "https://bobthecorp.compliance.tf/terraform-aws-modules/s3-bucket/aws?ruleset=ignore-scanner-tags"
5.3

One download, on a framework host

Per-request query parameters drop one of your organization's rules for that download, or add one. A bare ?rules= list replaces your rules rather than adding to them, so it cannot be combined with ?add_rules=.

# add one rule for this download
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?add_rules=lifecycle_prevent_destroy_data"

# drop one of your organization's rules for this download
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?rules=-lifecycle_ignore_tags"

The SOC 2 host serves accounts on a trial or a paid plan. Dropping a rule needs an organization with rules of its own.

5.4

Plans

Per-request rules, ?rules= on the source line, work on the free tier: you choose which to include. A Baseline and named rulesets need an organization on a trial, paid or enterprise plan. See pricing and Baseline rules and rulesets.

Read the diff before a module changes. Check what arrived after.

The Playground and the Preview API show the exact change a rule makes before anyone downloads the module. Every module built for you carries a manifest of the rules it received.

6

Review and record

6.1

Rules Playground

A browser view of a rule's effect on a module before download: real engine, real module, no account. The preview reads the module root only.

6.2

Rules Preview API

  • Public route: no account, allowlisted pinned module versions, ?rules= required, rate limited.
  • Private route: your dashboard session, any catalog module, your org Baseline plus ?add_rules= or ?ruleset=.
6.3

The manifest

A module compliance.tf builds for you carries a compliancetf-manifest.json, written by terraform init into the module directory under .terraform/modules/. It names every rule the build received with an outcome per rule, the file paths it changed, and a variant fingerprint, so you can check that two environments pulled the same rules-applied artifact. It records files, not resource addresses.

6.4

Version and exit

The module version you run stays in the source line, with the same inputs and outputs. Going back is the same edit in reverse: restore the old source line and run terraform init. Resource addresses do not change, so there is no state migration.

Start with the rule your fork carries.

Pick your module and rule in the Playground. If the diff is the block you maintain, change one source line and re-plan.

No credit card or AWS account needed to start.

7

Next step

7.1

To replace a fork

  • Read the diff in the Playground for your module and the rule your fork carries.
  • Register at the free trial page and create a registry token. For an https:// source line the token goes in ~/.netrc or a CI secret, never in the source line.
  • Put the version you run today into the URL with the rule, run terraform init and terraform plan, and delete the fork once the downloaded HCL matches the block it carried.

Not convinced yet, or missing a feature you need? Send us an email - we really want to hear your feedback.