SSM parameter lookup in CloudFormation stacks
Table of Contents
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.
| |
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.
| |
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.
| |
| |
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.