AWS Systems Manager Parameter Store is a central store for configuration data and secrets. Terraform is the most popular cross cloud framework for infrastructure as code. The combination is powerful for declaring parameters in code while still allowing runtime processes to mutate those values without breaking drift detection. The reference material covers how to create parameters with Terraform, how to prevent Terraform from reverting externally changed values, how to use the community module terraform-aws-modules/ssm-parameter/aws, and how to migrate secrets from Secrets Manager to Parameter Store for cost reduction.
The core tension is ownership. Terraform wants to be the source of truth. SSM Parameters are often touched by applications, CI pipelines, GitHub Actions building docker images and pushing to ECR, or Lambda functions that update a stable tag. When Terraform runs terraform apply it will revert any change that was not made through Terraform unless the configuration explicitly opts out of value enforcement. That revert behavior is predictable and repeatable. The value will be set back to the initial value declared in Terraform. The impact for an operator is unexpected overwrites of live configuration, broken deployments, and loss of trust in infrastructure as code.
Managing infrastructure using code is accepted best practice in the cloud. The challenge appears when resources that are updated by external processes need to remain under Terraform ownership for creation and metadata. This is particularly true for AWS Systems Manager SSM Parameters when parameters are modified by other applications or workflows. The solution is not to create the parameter directly in the console or the AWS CLI. That is not ideal as the param is not tracked. The solution is to keep creation and metadata in Terraform and decouple value enforcement.
The Challenge of SSM Parameters Updated Outside Terraform
There are times when you need to set an initial value for a SSM Parameter, but another process will maintain it. When using Terraform, each time you run terraform apply, the value will be reverted to the initial value.
The real world consequence is that a team can lose the ability to publish a new stable tag via GitHub Actions and have that change immediately undone by the next Terraform run. Operations teams see a parameter value flip back and forth between the desired live value and the Terraform declared value. Auditing becomes difficult because the state file reflects a value that does not match reality.
A simple reproduction shows the behavior. Terraform will perform the following actions:
```
awsssmparameter.example will be created
- resource "awsssmparameter" "example" {
- arn = (known after apply)
- data_type = (known after apply)
- id = (known after apply)
- insecure_value = (known after apply)
- key_id = (known after apply)
- name = "example"
- tags_all = (known after apply)
- tier = (known after apply)
- type = "String"
- value = (sensitive value)
- version = (known after apply)
}
```
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ ssmparamvalue = "set by terraform"
After apply:
aws_ssm_parameter.example: Creating...
aws_ssm_parameter.example: Creation complete after 2s [id=example]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
ssm_param_value = "set by terraform"
The AWS CLI confirms the parameter:
aws ssm get-parameter --name example
The output shows:
{
"Parameter": {
"Name": "example",
"Type": "String",
"Value": "set by terraform",
"Version": 1,
"LastModifiedDate": "2024-09-17T08:03:23.914000+10:00",
"ARN": "arn:aws:ssm:us-east-1:012345678910:parameter/example",
"DataType": "text"
}
}
To simulate external update, the value is changed via the AWS CLI. On the next Terraform apply the resource shows modifications:
aws_ssm_parameter.example: Modifications complete after 1s [id=example]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
ssm_param_value = "set by terraform"
The value has reverted to set by terraform. The impact layer is that any external update is treated as drift and corrected. This is desirable for immutable configuration but destructive for parameters meant to be a mutable pointer.
The contextual layer connects this to deployment pipelines. When terraform refreshes its state, it pulls the current value of awsssmparameter.example.value from SSM. That means Terraform can read the current value even if it does not enforce it. The state remains in sync with reality for reading, while write enforcement can be disabled.
Lifecycle Meta Argument and Ignore Value Changes
Terraform's lifecycle meta argument provides a way to tell Terraform to ignore changes to specific attributes. For SSM Parameters the critical attribute is value.
The article Managing AWS SSM Parameters with Terraform with External Updates demonstrates how to use Terraform's lifecycle meta argument and demonstrate how it can be used to solve this challenge. The benefit of using a shared SSM Parameter like this is that we can reference the current value of the param. When terraform refreshes its state, it pulls the current value of awsssmparameter.example.value from SSM.
We could have GitHub Actions building docker images and pushing them to ECR. Actions could update the current stable tag in SSM. Terraform could pull this value when deploying our ECS task or Lambda function. While a awsssmparameter data source could be used here, it has some downsides. Terraform fails if the data source does not exist. This means we can not create the ECR repo and other resources until the param exists. We have gone full circle and are back to creating the value manually, which we want to avoid.
Regular rotation of credentials and other secrets is an important security hygiene habit. Tracking and managing infrastructure as code ensures consistency in environments. Using lifecycle arguments for SSM Params allows us to do both, without conflict or surprises.
The community module exposes this via ignorevaluechanges. Example usage:
module "list" {
source = "terraform-aws-modules/ssm-parameter/aws"
ignore_value_changes = true
name = "my-parameter-ignore-value-changes"
value = "some-value"
}
With ignorevaluechanges set to true, Terraform will create the parameter with the initial value and will not revert subsequent external changes. Metadata such as name, type, tier, tags, and kms key can still be managed by Terraform. Value is read from SSM on refresh but never written back.
The impact for teams is that they can declare a parameter once, seed it with a default, and then allow operators or automated systems to update it safely. Terraform remains the owner of the resource lifecycle, preventing accidental deletion, while value mutations are tolerated.
Terraform Community Module Patterns
The module which creates AWS SSM Parameters on AWS is terraform-aws-modules/ssm-parameter/aws.
- One of multiple SSM Parameters can be created
- Value type guesser
- Allow SSM Parameter to ignore changes in the value
- Wrapper module which allows managing multiple resources with less code
The module supports string, secure string, and string list parameters. Secure type can be toggled.
Example string parameter:
module "string" {
source = "terraform-aws-modules/ssm-parameter/aws"
name = "my-parameter"
value = "some-value"
}
Example secure parameter:
module "secret" {
source = "terraform-aws-modules/ssm-parameter/aws"
name = "my-secret-token"
value = "secret123123!!!"
secure_type = true
}
Example list parameter:
module "list" {
source = "terraform-aws-modules/ssm-parameter/aws"
name = "my-list-parameter"
values = ["item1", "item2"]
}
The module can be used with locals for bulk creation.
```
locals {
parameters = {
#
String
#
"stringsimple" = {
value = "stringvalue123"
}
"string" = {
type = "String"
value = "stringvalue123"
tier = "Intelligent-Tiering"
allowedpattern = "[a-z0-9_]+"
}
#
SecureString
#
"secure" = {
type = "SecureString"
value = "secret123123!!!"
tier = "Advanced"
description = "My awesome password!"
}
"secureencryptedtrue" = {
securetype = true
value = "secret123123!!!"
keyid = "c938de44-1c09-4c91-89fd-b5881f06f317"
}
#
StringList
#
"listasautoguesstype" = {
values = ["item1", "item2"]
}
"listasjsonencodedstring" = {
type = "StringList"
value =
```
The impact is reduced boilerplate and consistent naming, typing, and tagging across hundreds of parameters. The contextual layer ties this to organizational standards where teams want hierarchical naming and automated type detection.
Parameter Type Configurations and Tiers
SSM Parameter Store supports String, SecureString, and StringList. Type selection determines encryption and access controls.
String parameters are stored in plaintext and are suitable for configuration values. SecureString parameters are encrypted with AWS KMS. Standard tier is free for up to 10,000 parameters. Advanced tier costs $0.05/parameter/month. You only need it for policies, expiration, or >10K parameters. Standard is free for up to 10,000 parameters.
Naming convention matters. Use hierarchical paths like /app/env/secret-name. SSM supports path-based GetParametersByPath to fetch all secrets for an app at once.
Tier selection impacts cost and features. Intelligent-Tiering allows automatic movement between Standard and Advanced tiers based on access patterns. Allowed pattern can be enforced with a regex such as [a-z0-9_]+.
KMS is the same for both Secrets Manager and Parameter Store. Both services use KMS for encryption. Security posture does not change when migrating.
IAM granularity is path based. An example policy:
arn:aws:ssm:*:*:parameter/myapp/prod/*
restricts access to just prod secrets.
The impact is lower operational cost and finer access control. The contextual layer is that Terraform can declare tier and kms key id once and allow applications to update values without incurring additional cost.
Secrets Manager Migration Economics
A common cost optimization is to migrate secrets that do not require automatic rotation from Secrets Manager to SSM Parameter Store.
| Secrets Count | Secrets Manager/Year | SSM Parameter Store/Year | Annual Savings |
|---|---|---|---|
| 20 | $96 | $0 | $96 |
| 50 | $240 | $0 | $240 |
| 100 | $480 | $0 | $480 |
| 200 | $960 | $0 | $960 |
Bottom line: If your secrets don't rotate automatically, you're paying a $0.40/month tax per secret for nothing. SSM Parameter Store gives you the same encryption, same access control, same SDK experience — for free.
Migration guidance from the reference material:
- Monitor for 48 hours
- Check CloudTrail — no more GetSecretValue calls
- THEN remove Secrets Manager resources from Terraform
- Terraform apply to delete old secrets
Warning: Never delete the Secrets Manager secret before confirming the app reads from SSM. Run both in parallel during the transition.
Don't migrate rotation-dependent secrets. If RDS credentials auto-rotate via Secrets Manager, leave them. The rotation Lambda integration isn't worth rebuilding.
The impact is direct cost reduction with no security loss. The contextual layer is that Terraform can manage the Parameter Store parameters with ignorevaluechanges while the application continues to read, ensuring zero downtime cutover.
Terraform SSM Resource Catalog
Terraform provider aws includes a broad SSM resource set.
12 Terraform resources and 7 data sources available.
Resources include:
- awsssmactivation
ResourceManages an Ssm Activation resource. - awsssmassociation
ResourceManages an Ssm Association resource. - awsssmdefaultpatchbaseline
ResourceManages an Ssm Default Patch Baseline resource. - awsssmdocument
ResourceManages an Ssm Document resource. - awsssmmaintenance_window
ResourceManages an Ssm Maintenance Window resource. - awsssmmaintenancewindowtarget
ResourceManages an Ssm Maintenance Window Target resource. - awsssmmaintenancewindowtask
ResourceManages an Ssm Maintenance Window Task resource. - awsssmparameter
ResourceManages an Ssm Parameter resource. - awsssmpatch_baseline
ResourceManages an Ssm Patch Baseline resource. - awsssmpatch_group
ResourceManages an Ssm Patch Group resource. - awsssmresourcedatasync
ResourceManages an Ssm Resource Data Sync resource. - awsssmservice_setting
ResourceManages an Ssm Service Setting resource.
The awsssmparameter resource is the central resource for parameter management. The data sources allow querying existing parameters without failing if they do not exist, which is important for optional dependencies.
The impact is that Terraform can model entire SSM operations beyond parameters, including patch compliance and session management. The contextual layer is that parameter modules sit on top of the raw resource, abstracting repetitive configuration.
Operational Workflow Examples
A typical workflow:
- Terraform creates the parameter with initial value and ignorevaluechanges true.
- Application or CI updates the parameter value via AWS CLI or SDK.
- Terraform plan shows no changes to value, only metadata drift if any.
- Terraform can reference the current value in other resources, e.g., ECS task definition or Lambda environment variables.
Session management example from the reference material shows a module configuration for SSM sessions:
aws ssm start-session \
--target "<instance_id_here>"
--document-name "<name_of_created_session_document>"
Version constraints:
```
| Name | Version |
|---|---|
| terraform | >= 0.14.11 |
| aws | >= 5.15.0 |
| Name | Version |
| --- | --- |
| aws | 5.33.0 |
No modules.
```
Resources created:
```
| Name | Type |
|---|---|
| awscloudwatchloggroup.ssmlog_group | resource |
| awsssmassociation.main | resource |
| awsssmdocument.custom | resource |
| awsssmdocument.session_preferences | resource |
```
Inputs:
```
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| association_name | The descriptive name for the association. | string | null | no |
| cloudwatchencryptionenabled | If set to true, the log group you specified in the cloudWatchLogGroupName input must be encrypted. | bool | true | no |
| cloudwatchstreamingenabled | If set to true, a continual stream of session data logs are sent to the log group you specified in the cloudWatchLogGroupName input. If set to false, session logs are sent to the log group you specified in the cloudWatchLogGroupName input at the end of your sessions. | bool | true | no |
| content | The JSON or YAML content of the document. | string | "" | no |
| createcustomdocument | Specify whether to create ssm document with custom settings | bool | false | no |
| createsessionpreferences | Whether to create session preferences | string | false | no |
| document_format | The format of the document |
```
This illustrates how SSM documents and associations are managed alongside parameters in the same Terraform stack.
Conclusion
Managing AWS SSM Parameters with Terraform while allowing external updates is achieved by separating creation and metadata ownership from value enforcement. The lifecycle meta argument or the module option ignorevaluechanges ensures that Terraform will not revert externally modified values. The terraform-aws-modules/ssm-parameter/aws module provides a concise interface for string, secure string, and list parameters with support for tiers, KMS keys, and allowed patterns.
Path-based naming conventions like /app/env/secret-name enable bulk retrieval via GetParametersByPath and fine grained IAM policies such as arn:aws:ssm:::parameter/myapp/prod/*. Standard tier is free for up to 10,000 parameters, while Advanced tier costs $0.05/parameter/month and is only required for advanced features. Migration from Secrets Manager to SSM Parameter Store for non-rotating secrets yields immediate savings, with documented savings of $96 per year for 20 secrets, $240 for 50, $480 for 100, and $960 for 200 secrets.
The Terraform AWS provider offers 12 SSM resources and 7 data sources, covering parameters, documents, associations, maintenance windows, patch baselines, and service settings. Using Terraform to create parameters with ignorevaluechanges, allowing CI pipelines to update values, and referencing current values in deployment configurations creates a sustainable pattern where infrastructure as code tracks resources without fighting runtime changes. Regular rotation of credentials remains important, and tracking infrastructure as code ensures consistency across environments. Using lifecycle arguments for SSM Params allows both security hygiene and operational flexibility without conflict or surprises.