CDK’s "escape hatches" let you directly manipulate the CloudFormation resources it generates, but they’re a powerful tool that can easily break your infrastructure if misused.
Let’s see this in action. Imagine you have a standard aws_s3_bucket resource in your CDK app, and you need to set a specific, non-standard CloudFormation property that CDK doesn’t have a direct construct for, like BucketEncryption.ServerSideEncryptionConfiguration. CDK usually handles S3 bucket encryption automatically, but let’s say you need to force a specific KMS key.
from aws_cdk import (
aws_s3 as s3,
aws_kms as kms,
Stack,
RemovalPolicy
)
from constructs import Construct
class EscapeHatchExampleStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# Create a KMS key
kms_key = kms.Key(self, "MyKmsKey",
enable_key_rotation=True,
removal_policy=RemovalPolicy.DESTROY)
# Standard S3 bucket creation
bucket = s3.Bucket(self, "MyBucket",
bucket_name="my-unique-cdk-escape-hatch-bucket",
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True)
# *** The Escape Hatch ***
# Access the underlying CloudFormation resource
cfn_bucket = bucket.node.default_child
# Override or add properties directly
cfn_bucket.add_override("Properties.BucketEncryption", {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": kms_key.key_arn
}
}
]
})
# You can also add tags, for example
cfn_bucket.add_override("Tags", [
{"Key": "Environment", "Value": "Dev"},
{"Key": "ManagedBy", "Value": "CDK"}
])
When you deploy this stack, CDK first synthesizes the CloudFormation template. The cfn_bucket.add_override(...) calls don’t create new resources; they directly modify the AWS::S3::Bucket resource definition within the generated CloudFormation. The node.default_child is the key here – it gives you a direct handle to the low-level CloudFormation resource object that CDK is managing.
The problem this solves is when CDK’s opinionated abstractions don’t cover a specific CloudFormation property you need. For instance, maybe you need to set PublicAccessBlockConfiguration in a very specific way, or configure LifecycleRule with properties not exposed by the s3.Bucket construct. Escape hatches provide a direct bridge. You identify the CloudFormation property name (e.g., BucketEncryption, Tags), and use add_override with the exact structure CloudFormation expects.
Internally, add_override works by modifying the cdk.CfnResource object. This object represents the CloudFormation resource. When cdk synth runs, it serializes these CfnResource objects into the final CloudFormation JSON or YAML. The add_override method essentially injects or replaces parts of the Properties attribute of that CloudFormation resource. It’s like saying, "CDK, I know you generate this AWS::S3::Bucket resource, but here’s how I want the Properties section to look for this specific field."
The one thing most people don’t realize is that add_override can target any property in the CloudFormation resource definition, not just top-level ones. You can use it to modify nested objects or arrays within the Properties. For example, if you wanted to add a specific CorsConfiguration rule to the S3 bucket, you’d use cfn_bucket.add_override("Properties.CorsConfiguration.CorsRules", [...]), providing the full structure CloudFormation expects for that rule. It’s a powerful way to customize deeply.
The next thing you’ll likely run into is trying to override a property that CDK also manages through its own constructs. If you set BucketEncryption via add_override and also try to configure encryption using bucket.encryption_key = kms_key (which would also generate BucketEncryption properties), CloudFormation will likely reject the deployment due to conflicting properties for the same resource.