The `Conditions` section gates whether resources, outputs, and individual property values exist in the deployed stack. It's how a single template serves dev / staging / prod without forking, and how optional features are gated by parameter flags. ## The three-step pattern 1. **Define an input** in `Parameters` (or use a pseudo parameter) — typically an enum like `EnvType: [test, prod]` 2. **Define a condition** in `Conditions` using `Fn::Equals` (and optionally `And`/`Or`/`Not`) — gives the condition a logical name like `IsProduction` 3. **Apply the condition** in `Resources` / `Outputs` via the `Condition:` key, or inline in property values via `Fn::If` ## The condition functions | Function | Returns true when | Notes | |----------|-------------------|-------| | `Fn::Equals` | Two values are exactly equal | **Exact string match only** — no regex, no type coercion | | `Fn::And` | All listed conditions/values are true | Up to 10 args | | `Fn::Or` | Any listed condition/value is true | Up to 10 args | | `Fn::Not` | The single nested condition is false | One arg | | `Fn::If` | Returns one of two values based on a condition | Used in property values, not in Conditions section | | `Fn::ForEach` | Iterates over a list (newer) | Used for repeated structures | `Fn::If` is special — it's allowed in **metadata attribute, update policy attribute, and property values** in `Resources` and `Outputs`. It's NOT allowed in arbitrary template positions. ## Two ways to apply a condition **Resource/Output level — `Condition:` key**: ```yaml NewVolume: Type: AWS::EC2::Volume Condition: IsProduction # entire resource only created if IsProduction is true Properties: Size: 100 ``` When the condition is false, the resource is not created at all. References to it from other resources will fail at template-validate time unless those references are also gated. **Property level — `Fn::If`**: ```yaml Properties: InstanceType: !If [IsProduction, c5.xlarge, t3.small] ``` Two return values: one for true, one for false. To **omit** the property entirely (vs setting to a value), use `!Ref AWS::NoValue` as the false branch — see [[CFN Pseudo Parameters]]. ## Composing conditions Conditions can reference other conditions via the `Condition:` key (in JSON: `{"Condition": "OtherConditionName"}`): ```yaml Conditions: IsProduction: !Equals [!Ref EnvType, prod] IsFeatureEnabled: !Equals [!Ref FeatureFlag, enabled] IsProdAndFeatureEnabled: !And - !Condition IsProduction - !Condition IsFeatureEnabled ``` ## What you can reference in conditions | Allowed | Not allowed | |---------|-------------| | Input parameters (`!Ref ParamName`) | Resource logical IDs | | Pseudo parameters (`!Ref AWS::Region`) | Resource attributes (`!GetAtt`) | | Other conditions (`!Condition Name`) | Outputs | | Mapping values (`!FindInMap`) | | The exclusion of resources and `Fn::GetAtt` is fundamental — conditions are evaluated **before** any resource is provisioned, so resource attributes don't exist yet. ## Re-evaluation on update Every stack update re-evaluates all conditions: - Resources where the condition flipped to true → **created** - Resources where the condition flipped to false → **deleted** (data loss for stateful resources) - Resources whose condition stayed true → updated normally This is powerful but dangerous: flipping `IsProduction` from true to false will delete every production-only resource. Test condition flips with change sets first. ## The "can't update conditions alone" gotcha > During a stack update, you can't update conditions by themselves. You can update conditions only when you include changes that add, modify, or delete resources. If you change only the condition logic (e.g., adjusting an `Fn::Equals` comparison), CFN refuses the update. Workaround: bundle a trivial resource change in the same update. ## Common patterns **Environment promotion** — single template, multiple environments: ```yaml Conditions: IsProduction: !Equals [!Ref EnvType, prod] Resources: EC2Instance: Properties: InstanceType: !If [IsProduction, c5.xlarge, t3.small] DataVolume: Type: AWS::EC2::Volume Condition: IsProduction # only prod gets the extra volume ``` **Optional resource based on parameter**: ```yaml Conditions: CreateBucket: !Not [!Equals [!Ref BucketName, ""]] Resources: Bucket: Type: AWS::S3::Bucket Condition: CreateBucket ``` **Combine conditions for AND-of-features**: ```yaml Conditions: CreateBucketPolicy: !And - !Condition IsProduction - !Condition CreateBucket ``` ## When NOT to use Conditions If you have more than ~3 conditions interacting, the template becomes hard to reason about. Alternatives: - **Mappings** for static lookup tables (region → AMI, env → instance type) — accessed via `Fn::FindInMap` - **Separate templates** when the resource topology is materially different per environment - **Macros / Transforms** for templated generation - **CDK / SAM** if you need real conditional language constructs ## Related - [[CFN Pseudo Parameters]] - [[CFN Ref vs Fn-GetAtt]] - [[CFN Update Behaviors and the Replacement Trap]]