Write your own policy packs
If Pulumi’s pre-built policy packs don’t meet your requirements, you can write custom policy packs. Custom policies let you enforce any compliance, security, or operational rule.
Policies can be written in TypeScript/JavaScript (Node.js) or Python and can be applied to Pulumi stacks written in any language. Learn more about language support for policies.
Creating a Policy Pack with Neo
This guide walks you through creating a policy pack manually, but Neo can help streamline the process.
Neo can generate policy pack content tailored to your preferred programming language and cloud providers, allowing you to quickly build policies that meet your specific requirements while reducing errors. When paired with the GitHub App, Neo can even open pull requests directly in your repository.
Here are some example prompts to inspire your workflow:
“Create a boilerplate TypeScript policy pack at
<GitHub Repository>”
“Create a policy to enforce encryption of S3 buckets”
“Create a policy that requires environment tagging on all Google Cloud resources”
Prerequisites
Before authoring your first policy pack, ensure you have:
- Pulumi CLI installed.
- For TypeScript/JavaScript policies: Node.js installed.
- For Python policies: Python installed.
- (Optional) Access to Pulumi Cloud if you want to publish and centrally manage policy packs. Not required for local policy pack usage with open source Pulumi.
- An understanding of Policy as Code core concepts.
Creating a policy pack
Create your first policy pack:
Create a directory for your policy pack and navigate to it.
$ mkdir policypack && cd policypackCreate a new TypeScript project:
$ pulumi policy new aws-typescriptReplace the generated policy in
index.tswith this example, which demonstrates a clearer pattern for organizational policy enforcement:Each policy must have:
- A unique name, description, and validation function
- A validation function (this example uses
validateResourceOfTypeto run only for AWS S3 bucket resources) - An enforcement level set at the policy pack level (applies to all policies) or per policy (overrides the pack level)
For more information on all available fields, see policy metadata.
import * as aws from "@pulumi/aws"; import { PolicyPack, validateResourceOfType } from "@pulumi/policy"; // Create a new policy pack. new PolicyPack("policy-pack-typescript", { // Specify the policies in the policy pack. policies: [{ // The name for the policy must be unique within the pack. name: "s3-bucket-prefix", // The description should document what the policy does and why it exists. description: "Ensures S3 buckets use the required naming prefix.", // The enforcement level can be "advisory", "mandatory", or "disabled". An "advisory" enforcement level // simply prints a warning for users, while a "mandatory" policy will block an update from proceeding, and // "disabled" disables the policy from running. enforcementLevel: "mandatory", // The validateResourceOfType function allows you to filter resources. In this case, the rule only // applies to S3 buckets and reports a violation if the bucket prefix doesn't match the required prefix. validateResource: validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => { const requiredPrefix = "mycompany-"; const bucketPrefix = bucket.bucketPrefix || ""; if (!bucketPrefix.startsWith(requiredPrefix)) { reportViolation( `S3 bucket must use '${requiredPrefix}' prefix. Current prefix: '${bucketPrefix}'`); } }), }], });
Create a directory for your policy pack and navigate to it.
$ mkdir policypack && cd policypackCreate a new Python project:
$ pulumi policy new aws-pythonVirtual environment configuration: Python policy packs use a virtual environment specified in
PulumiPolicy.yaml. The default name isvenv. If you use a different name (like.venv), updatePulumiPolicy.yaml:runtime: name: python options: virtualenv: .venvReplace the generated policy in
__main__.pywith this example, which demonstrates a clearer pattern for organizational policy enforcement:Each policy must have:
- A unique name, description, and validation function
- An enforcement level set at the policy pack level (applies to all policies) or per policy (overrides the pack level)
from pulumi_policy import ( EnforcementLevel, PolicyPack, ReportViolation, ResourceValidationArgs, ResourceValidationPolicy, ) # The validation function is called before each resource is created or updated. # In this case, the rule only applies to S3 buckets and reports a violation if the # bucket prefix doesn't match the required prefix. REQUIRED_S3_PREFIX = "mycompany-" def s3_bucket_prefix_validator(args: ResourceValidationArgs, report_violation: ReportViolation): if args.resource_type == "aws:s3/bucket:Bucket" and "bucketPrefix" in args.props: actual_prefix = args.props["bucketPrefix"] if not actual_prefix.startswith(REQUIRED_S3_PREFIX): report_violation( f"S3 bucket must use '{REQUIRED_S3_PREFIX}' prefix. Current prefix: '{actual_prefix}'") s3_bucket_prefix = ResourceValidationPolicy( # The name for the policy must be unique within the pack. name="s3-bucket-prefix", # The description should document what the policy does and why it exists. description="Ensures S3 buckets use the required naming prefix.", # The enforcement level can be ADVISORY, MANDATORY, or DISABLED. An ADVISORY enforcement level # simply prints a warning for users, while a MANDATORY policy will block an update from proceeding, and # DISABLED disables the policy from running. enforcement_level=EnforcementLevel.MANDATORY, # The validation function, defined above. validate=s3_bucket_prefix_validator, ) # Create a new policy pack. PolicyPack( name="policy-pack-python", # Specify the policies in the policy pack. policies=[ s3_bucket_prefix, ], )
You can find more example policy packs in the Pulumi examples repository.
Testing your policies
Write unit tests to verify your policies work correctly before publishing.
Here’s a simple test example using Mocha and assert:
describe("s3-bucket-prefix-policy", () => {
it("should pass when bucket has correct prefix", () => {
const args = getEmptyArgs();
args.type = "aws.s3.Bucket";
args.props.bucketPrefix = "mycompany-data";
assert.doesNotThrow(() => {
runResourcePolicy(s3BucketPrefixPolicy, args);
});
});
For a complete example including test helpers and setup, see the unit test policy example on GitHub.
Here’s a simple test example using pytest:
def test_bucket_with_correct_prefix():
"""Test that policy passes when bucket has correct prefix."""
args = ResourceValidationArgs(
resource_type="aws:s3/bucket:Bucket",
props={"bucketPrefix": "mycompany-data"},
urn="urn:pulumi:dev::test::aws:s3/bucket:Bucket::my-bucket",
name="my-bucket",
opts={},
provider="",
)
# Should not raise any violations
violations = []
def report_violation(message: str):
violations.append(message)
s3_bucket_prefix_validator(args, report_violation)
assert len(violations) == 0
For a complete example including additional test cases, see the unit test policy example on GitHub.
Resource validation vs stack validation
Pulumi policies validate at two scopes:
Resource validation policies
Resource validation policies run during pulumi preview or pulumi up, examining each resource before creation or update. These policies execute before the desired state is sent to the engine, which means they can block non-compliant resources during both preview and update operations.
Use resource validation policies when you need to:
- Enforce rules on specific resource types (e.g., “S3 buckets must have encryption enabled”)
- Validate resource properties before deployment
- Block individual non-compliant resources
Stack validation policies
Stack validation policies run after resource registration completes. These policies execute after resources have been created or updated, and only run during pulumi up (not during pulumi preview). They examine relationships between resources and enforce stack-wide rules.
Use stack validation policies when you need to:
- Validate relationships between resources (e.g., “databases must be in private subnets”)
- Enforce stack-wide rules (e.g., “stack must not exceed 50 resources”)
- Examine the complete resource graph
Most policies are resource validation policies. Stack validation policies are useful for more complex scenarios that require understanding the full context of your infrastructure.
Running policies locally
Test your policy pack locally before publishing.
Use the
--policy-packflag to specify your policy pack directory:If you need a test program, create one with
pulumi new aws-typescript. This creates an S3 bucket to test the policy.$ mkdir test-program && cd test-program $ pulumi new aws-typescriptFor AWS examples, ensure you have AWS credentials configured and set your region with
pulumi config set aws:region <region>.In the Pulumi program’s directory, run:
$ pulumi preview --policy-pack <path-to-policy-pack-directory>If the stack is compliant, the output shows which policy packs ran.
Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test-dev create + └─ aws:s3:Bucket my-bucket create Resources: + 2 to create Policy Packs run: Name Version aws-typescript (/Users/user/path/to/policy-pack) (local)Edit the stack code to specify a non-matching prefix:
const bucket = new aws.s3.Bucket("my-bucket", { bucketPrefix: "wrongprefix-", });Run
pulumi previewagain. This time, the policy violation blocks the preview:Previewing update (dev): Type Name Plan Info + pulumi:pulumi:Stack test-dev create 1 error + └─ aws:s3:Bucket my-bucket create Diagnostics: pulumi:pulumi:Stack (test-dev): error: preview failed Policy Violations: [mandatory] aws-typescript v0.0.1 s3-bucket-prefix (my-bucket: aws:s3/bucket:Bucket) Ensures S3 buckets use the required naming prefix. S3 bucket must use 'mycompany-' prefix. Current prefix: 'wrongprefix-'
Use the
--policy-packflag to specify your policy pack directory:If you need a test program, create one with
pulumi new aws-python. This creates an S3 bucket to test the policy.$ mkdir test-program && cd test-program $ pulumi new aws-pythonFor AWS examples, ensure you have AWS credentials configured and set your region with
pulumi config set aws:region <region>.In the Pulumi program’s directory, run:
$ pulumi preview --policy-pack <path-to-policy-pack-directory>If the stack is compliant, the output shows which policy packs ran.
Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test-dev create + └─ aws:s3:Bucket my-bucket create Resources: + 2 to create Policy Packs run: Name Version aws-python (/Users/user/path/to/policy-pack) (local)Edit the stack code to specify a non-matching prefix:
bucket = s3.Bucket('my-bucket', bucket_prefix="wrongprefix-")Run
pulumi previewagain. This time, the policy violation blocks the preview:Previewing update (dev): Type Name Plan Info + pulumi:pulumi:Stack test-dev create 1 error + └─ aws:s3:Bucket my-bucket create Diagnostics: pulumi:pulumi:Stack (test-dev): error: preview failed Policy Violations: [mandatory] aws-python v0.0.1 s3-bucket-prefix (my-bucket: aws:s3/bucket:Bucket) Ensures S3 buckets use the required naming prefix. S3 bucket must use 'mycompany-' prefix. Current prefix: 'wrongprefix-'
Configuring policy packs
Configuration makes policy packs flexible and reusable. Adjust enforcement levels, allowed values, and other settings without modifying code.
Enforcement levels
All policies support configurable enforcement levels. Set enforcement for all policies in a pack or override individual policies:
{
"all": {
"enforcementLevel": "advisory"
},
"critical-security-policy": {
"enforcementLevel": "mandatory"
}
}
As shorthand, specify enforcement levels directly:
{
"all": "advisory",
"critical-security-policy": "mandatory"
}
Enforcement levels:
- advisory — Issues warnings but allows deployments to proceed
- mandatory — Blocks deployments when violations are detected
- disabled — Skips policy evaluation entirely
Custom configuration
Policy authors define configuration schemas using JSON Schema, enabling administrators to customize policy behavior without code changes.
Example: Optional configuration with defaults
const examplePolicy: ResourceValidationPolicy = {
name: "example-policy-with-schema",
description: "Example policy with configurable message.",
configSchema: {
properties: {
message: {
type: "string",
minLength: 5,
maxLength: 50,
}
},
},
validateResource: validateResourceOfType(aws.s3.Bucket, (_, args, reportViolation) => {
const config = args.getConfig<{ message?: string }>();
const message = config.message || "Using default message";
reportViolation("Configured message: " + message);
}),
}
def example_policy_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
config = args.get_config()
message = config.get("message", "Using default message")
report_violation(f"Configured message: {message}")
example_policy = ResourceValidationPolicy(
name="example-policy-with-schema",
description="Example policy with configurable message.",
config_schema=PolicyConfigSchema(
properties={
"message": {
"type": "string",
"minLength": 5,
"maxLength": 50,
},
}
),
validate=example_policy_validator,
)
Example: Required configuration
To require configuration values, add them to the required list:
configSchema: {
properties: {
message: {
type: "string",
minLength: 5,
maxLength: 50,
}
},
required: ["message"],
}
config_schema=PolicyConfigSchema(
properties={
"message": {
"type": "string",
"minLength": 5,
"maxLength": 50,
},
},
required=["message"]
)
Using configuration files
Local execution
Pass configuration via JSON file:
config.json:
{
"all": "mandatory",
"example-policy-with-schema": {
"message": "Resources must follow naming standards"
}
}
Run with configuration:
pulumi preview --policy-pack <path-to-policy-pack> --policy-pack-config config.json
Pulumi Cloud configuration
After publishing, administrators configure policy packs through the Pulumi Cloud console or CLI.
Using the console:
- Navigate to your Policy Group
- Click Add Policy Pack
- Select the policy pack
- Fill in the configuration form (automatically validated against the schema)
- Save the configuration
Using the CLI:
Validate configuration before enabling:
pulumi policy validate-config <org>/<pack-name> <version> --config config.json
Enable with configuration:
pulumi policy enable <org>/<pack-name> <version> --config config.json
Or for a specific policy group:
pulumi policy enable <org>/<pack-name> <version> --config config.json --policy-group <group-name>
Publishing to your organization
After local validation, publish your policy pack to Pulumi Cloud. Policy enforcement runs automatically during preview and update for any stack using Pulumi Cloud.
Pulumi Cloud versions policy packs, enabling updates, rollbacks, and gradual rollouts.
From the policy pack directory, publish:
$ pulumi policy publish <org-name>Pulumi Cloud assigns a monotonic version number:
Obtaining policy metadata from policy plugin Compressing policy pack Uploading policy pack to Pulumi Cloud Publishing my-policy-pack to myorg Published as version 1.0.0
Managing policy pack versions
Policy pack versions are managed differently by language:
- TypeScript/JavaScript: Set the
versionfield inpackage.json - Python: Set the
versionfield inPulumiPolicy.yaml
Each version can only be published once.
Publishing a new version:
Update the version number:
- TypeScript: Edit
package.json:"version": "0.0.2" - Python: Edit
PulumiPolicy.yaml:version: 0.0.2
- TypeScript: Edit
Publish:
pulumi policy publish <org-name>
If you try to republish an existing version, you’ll see:
error: [400] Bad Request: Policy Pack "aws-typescript" (Version 0.0.1) has already been published.
Please specify a new version tag.
We recommend semantic versioning:
- Major (1.0.0 → 2.0.0): Breaking changes to policy behavior
- Minor (1.0.0 → 1.1.0): New policies added
- Patch (1.0.0 → 1.0.1): Bug fixes
After publishing, your policy pack appears in Pulumi Cloud’s policy pack list. Apply it to stacks or cloud accounts using policy groups. See Get Started with Pulumi Policy for details.
Considerations for authoring policies
Best practices for authoring policy packs:
Naming policies
Each policy within a policy pack must have a unique name. The name must be between 1 and 100 characters and may contain letters, numbers, dashes (-), underscores (_), or periods (.).
Policy assertions
Write policy assertions as complete sentences in imperative tone, specifying which resource violated the policy.
| ✅ Good | ❌ Poor |
|---|---|
| “The RDS cluster must specify a node type.” | “Specify a node type.” |
| “The RDS cluster must have audit logging enabled.” | “Enable audit logging.” |
| “S3 bucket must use ‘mycompany-’ prefix.” | “Use correct prefix.” |
This format helps users understand which resource failed and why.
Examples and resources
- Policy examples repository - Example policy packs demonstrating various implementation patterns
- Policy as Code overview
- Policy Metadata fields
Next steps
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.
