Skip to main content

SSM parameter lookup in CloudFormation stacks

Teams routinely store configuration in SSM Parameter Store — database endpoints, AMI IDs, certificate ARNs, account identifiers shared across stacks. Getting those values into CloudFormation templates sounds simple enough, but CloudFormation offers two distinct mechanisms for reading from SSM, and they resolve at different moments in the stack lifecycle. Choosing the wrong one — or misunderstanding when each resolves — produces stacks that silently use stale values or templates that behave inconsistently between environments.

Two mechanisms, two resolution phases

SSM-typed parameters

The first mechanism is an SSM-typed CloudFormation parameter. You declare a parameter in the Parameters section with a type from the AWS::SSM::Parameter::Value family (parameterized by the SSM value type, as shown in the example below), and CloudFormation fetches the current SSM value when you run create-stack or update-stack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Parameters:
  DatabaseHost:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /myapp/prod/db/host

Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Name: app
          Environment:
            - Name: DB_HOST
              Value: !Ref DatabaseHost

The value is resolved once, at the moment you submit the stack operation. CloudFormation stores it in the stack’s parameter history, and the stack continues to use that captured value until you run another update. If the SSM parameter changes between stack updates, the stack does not pick up the new value automatically. This is intentional: it keeps stack state reproducible and auditable, but it creates a staleness risk if you expect the stack to always reflect the current SSM value.

Dynamic references

The second mechanism is a dynamic reference. Instead of a Parameters declaration, you embed the SSM path directly in a resource property using the {{resolve:ssm:...}} syntax.

1
2
3
4
5
6
7
8
9
Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Name: app
          Environment:
            - Name: DB_HOST
              Value: "{{resolve:ssm:/myapp/prod/db/host}}"

Dynamic references resolve during resource provisioning, not when you submit the stack operation. The value retrieved is the current SSM value at the time CloudFormation processes that resource. For SecureString parameters, the {{resolve:ssm-secure:...}} variant retrieves the decrypted value and passes it directly to the resource property without exposing it in the stack event history.

The full syntax and supported locations are documented in the CloudFormation dynamic references guide.

Where each mechanism applies

The two mechanisms are not interchangeable. SSM-typed parameters can appear anywhere a CloudFormation parameter is used, including in Conditions and Fn::If expressions. This makes them useful for environment selection: you can store an environment name or a feature flag value in SSM, read it as a typed parameter, and use it to branch template structure.

Dynamic references cannot appear in the Conditions section or in Mappings. They are valid in most resource property values and a small number of other locations, but template-level branching is not one of them.

1
2
3
4
5
6
7
8
# Valid: SSM-typed parameter used in a Condition
Parameters:
  FeatureEnabled:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /myapp/feature/new-checkout

Conditions:
  IsFeatureEnabled: !Equals [!Ref FeatureEnabled, "true"]
1
2
3
4
# NOT valid: dynamic references cannot be used in Conditions
# This template will fail CloudFormation validation
Conditions:
  IsFeatureEnabled: !Equals ["{{resolve:ssm:/myapp/feature/new-checkout}}", "true"]

The parameters section reference covers SSM-typed parameter declarations in full.

The staleness problem

For SSM-typed parameters, the value in the stack reflects the SSM value at the time of the last stack update. If your deployment pipeline calls update-stack with the current parameter set, the parameter value is refreshed at that moment. But if you update the SSM value and do not trigger a stack update, the stack continues using the old value.

This produces incidents where the SSM value looks correct in the console, but running services are using an outdated value because the stack was never updated after the SSM change. The fix is usually a stack update that changes nothing except triggering the parameter refresh — possible, but easy to forget in practice.

Dynamic references avoid this problem for individual resource properties: the reference resolves to the current SSM value each time CloudFormation processes that resource. However, they cannot be used to drive template-level structure, so they do not fully substitute for SSM-typed parameters in all situations.

Common workarounds for the conditional gap

Teams that need to branch on an SSM value at template time typically work around the Conditions limitation in one of these ways.

Pre-resolve in the deploy pipeline. The pipeline script reads the SSM value and passes it as a CloudFormation parameter override. The template uses a conventional String parameter for the branch. This moves the SSM resolution step outside CloudFormation, which means the pipeline script becomes a dependency that must be maintained alongside the template.

Maintain a mapping. For a small, known set of environments, a Mappings block can substitute for a dynamic SSM lookup. Each environment key maps to the configuration values for that environment. This works for stable configuration but does not help when values change independently of the template or when the set of environments is not known in advance.

Lambda-backed custom resource. A Lambda function reads the SSM value at deployment time and returns it as a custom resource output attribute. The attribute can then be evaluated in an !Equals condition. This approach adds operational overhead: the Lambda function needs its own deployment, IAM role, and maintenance lifecycle.

Choosing between the two mechanisms

The practical guidance is straightforward:

  • Use SSM-typed parameters when the value drives template structure (conditions or mappings), and when you accept that the value is captured at update time.
  • Use dynamic references when you want the most current SSM value at provisioning time for a specific resource property and do not need it in a Condition.
  • For values that change frequently and must always be current in running services, consider whether a runtime configuration fetch inside the application is more appropriate than a CloudFormation-level resolution.

CloudFormation’s SSM integration is useful, but the two mechanisms have distinct scopes and resolution phases. Getting the most out of them means understanding when each one resolves and where in a template each one is valid.

Related extensions

Stop working around CloudFormation.

Browse the catalog, find the primitive your team keeps rebuilding, and license it once.