Restrict Instance Types
Records every aws_instance in the module source whose instance_type is a literal matching a denied family. Reports; does not block.
When to use this rule
Use this when: You pull modules that write instance_type directly into their resources, and you want a record whenever one of them names a GPU or high-memory family.
Do not use this when: You need the deployment stopped rather than recorded β this rule cannot block anything. Or you run ML and HPC workloads that legitimately require those families.
Why this rule exists
AWS instance families built for GPU computing (p3, p4, p5), memory-intensive workloads (x1, x2), and high-memory bare metal (u-*) run at many times the hourly rate of a general-purpose instance. A module that hardcodes one of them keeps charging for as long as nobody looks.
This rule gives you the signal at download time. It reads the module source and records any aws_instance whose instance_type is written in as a literal and matches a denied pattern. It is a detection control, not an enforcement one: the module arrives exactly as published and terraform apply still runs. What you get is a finding you can act on before the module reaches production.
Affected resources
| Resource | Service | Why |
|---|---|---|
aws_instance | Amazon EC2 | instance_type determines compute cost |
What this rule does
Reports β it does not rewrite. Every resource in the module source is scanned, and a literal instance_type value matching a denied pattern is recorded as a finding. The module is delivered byte-for-byte unchanged, and terraform init still succeeds.
Patterns are shell-style globs matched against the whole value, so p3.* matches p3.2xlarge but not p3dn.24xlarge.
Denied patterns: p3.*, p4.*, x1.*, x2.*, u-*
What a finding looks like
The rule reads the module source. A literal value that matches a denied pattern produces a finding:
resource "aws_instance" "this" {
instance_type = "p3.8xlarge"
}! resource.aws_instance.this: GPU and high-memory instance types are not permitted without approvalA value outside the deny list produces nothing:
resource "aws_instance" "this" {
instance_type = "m5.xlarge"
}Either way the module source is delivered unchanged. Nothing is blocked, rewritten, or added.
Real-world scenario
A machine-learning proof of concept left a p3.8xlarge running in a staging account. Nothing in the pipeline flagged it, and it surfaced weeks later in a cost review. The module that created it had the instance type written straight into the resource β exactly the case this rule reports.
Known limits
- Reports only. The module source is delivered unchanged, no validation block is added, and
terraform init,plan, andapplyall proceed normally. - Reads literal values only.
instance_type = var.instance_type, alocal, adatalookup, an interpolation, or a function call is skipped, because the value is not knowable at download time. Most terraform-aws-modules take the instance type from a variable, so the rule finds nothing in them. - Reads the module source, not your own configuration. An
instance_typeyou set in your root module or pass into amoduleblock is never examined. - The default patterns are globs matched against the whole value, and several match less than their names suggest:
p4.*matches no released p4 instance (they are namedp4dandp4de),x2.*matches no released x2 instance (x2gd,x2idn,x2iedn,x2iezn), andp3dn.24xlarge, thex1efamily, and the whole p5 family are not matched either. - Only checks
instance_typeonaws_instance. Does not checkaws_launch_template,aws_autoscaling_group,aws_sagemaker_endpoint_configuration, or ECS task size. - Does not restrict other expensive resource types (e.g., large RDS instances, Redshift nodes, OpenSearch domains).
- Only
.tfand.tofufiles are read. Modules written in JSON syntax (.tf.json,.tofu.json) are left untouched. - Only the module's own directory and its first-level
modules/*submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
Compliance framework support
This rule is not a compliance control. It supports these framework objectives as an operational safeguard:
| Framework | Controls | Role |
|---|---|---|
| SOC 2 | CC6.3 | Provides a detection signal for disallowed resource configurations in module source |
Default configuration
These are the values the rule ships with. They are not settable from a module source URL: ?rules= carries rule names only, so a per-request enable uses exactly the defaults below. Your organization sets them per rule, in its Baseline or in a ruleset, from the Operational Rules page; see Baseline rules and rulesets.
| Parameter | Type | Default | Description |
|---|---|---|---|
denied | list(string) | ["p3.*", "p4.*", "x1.*", "x2.*", "u-*"] | Denied instance type patterns |
message | string | "GPU and high-memory instance types are not permitted without approval" | Validation error message |
How to enable
On a framework host, per request:
Add ?rules=resource_restrict_instance_types to your HTTPS module source. Use the full namespaced id. A bare resource_restrict_instance_types matches no rule: it is skipped server-side and your terraform init still succeeds, without the rule.
module "example" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=resource_restrict_instance_types"
}On your organization's host:
Add the rule to the organization's Baseline, which applies to every module served from the bare organization form <alias>.compliance.tf/<namespace>/<name>/<provider>, or to a named ruleset that one module selects with ?ruleset=<name> on the HTTPS form of that address. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead: there the rule goes into the config draft and reaches the environment by promotion. On both organization forms ?rules= is refused with a 400 β including the subtractive - prefix β rather than dropped, so you cannot mistake an ignored override for an applied one. Change the configuration, not the URL. See Baseline rules and rulesets for the configuration flow, and registry resolution for how the address forms differ.
Failure modes
| Scenario | Result |
|---|---|
| A module hardcodes a denied instance type | The rule records a finding against that resource address. The module is delivered unchanged and the deployment proceeds. |
A module sets instance_type from a variable | No finding. The value is not knowable when the rule runs, so the resource is skipped. |
| AWS releases a new expensive family | Not matched. The deny list is a fixed set of patterns and has to be extended before the new family is recognised. |
Terraform and OpenTofu compatible
This rule works with both Terraform (1.x+) and OpenTofu (1.6+). It reads ordinary HCL, and anything it changes stays ordinary HCL, so the module behaves the same under either tool.
Help us improve this page
Operational Rules are a new feature. We'd love your feedback on this rule page β what's useful, what's missing, what's confusing. Share feedback.