Migrating SSM Parameters Between AWS Accounts
AWS Systems Manager Parameter Store is a convenient place to keep application configuration: API endpoints, feature flags, database settings, and encrypted secrets.
Eventually, though, you may need to move those parameters somewhere else.
Typical examples include:
- Migrating workloads between AWS accounts.
- Creating a new staging or production account.
- Rebuilding an environment.
- Moving configuration as part of an AWS Organizations restructuring.
- Copying an existing application's configuration into a new account.
Parameter Store gives us APIs for reading and writing parameters, but moving an entire parameter hierarchy between accounts still usually means writing some glue code.
I built a small CLI called ssm-bulk to make this easier:
GitHub: https://github.com/ilich/aws-samples/tree/main/ssm/bulk_parameters
The tool exports parameters recursively under an SSM path into CSV and can then import them into another account. It preserves String, StringList, and SecureString parameter types. SecureString values are decrypted during export.
The workflow is straightforward: export from the source account, review or modify the generated CSV, perform a dry run, and import it into the destination account.
Installing the tool
The tool requires Python 3.13+ and uv.
Clone the repository and install it:
cd aws-samples/ssm/bulk_parameters
uv tool install .
After installation, the CLI is available globally:
ssm-bulk --help
You also need AWS credentials for both the source and destination accounts. AWS profiles are particularly convenient for migrations.
For example:
[profile old-account]
...
[profile new-account]
...
Before starting, I recommend checking that both profiles point to the accounts you expect:
aws sts get-caller-identity --profile old-account
aws sts get-caller-identity --profile new-account
Accidentally reversing source and destination accounts is not the most entertaining way to spend an afternoon.
Step 1: Export parameters from the source account
Suppose the application stores its configuration under /my-app/, with parameters such as:
/my-app/database/host/my-app/database/username/my-app/database/password/my-app/api/endpoint/my-app/features/new-checkout
Export the complete hierarchy:
ssm-bulk pull /my-app/ \
--profile old-account \
--region us-east-1 \
--file my-app.csv
pull recursively retrieves every parameter whose name begins with /my-app/.
The resulting CSV contains the parameter name, value, type, and keep flag. The supported parameter types are String, StringList, and SecureString.
For example:
name,value,type,keep
/my-app/api/endpoint,https://api.example.com,String,False
/my-app/database/host,db.internal.example.com,String,False
/my-app/database/password,super-secret-password,SecureString,False
/my-app/database/username,myapp,String,False
The important part is that the parameter type is preserved. A SecureString doesn't silently become a normal String during migration.
Be careful with SecureString parameters
There is one important consequence of the export process.
During pull, SecureString parameters are decrypted so they can later be created in another account.
That means the generated CSV contains those secret values in plaintext.
Treat the file accordingly:
- Don't accidentally commit it to the code repository.
- Remove them when the migration is finished.
Your source AWS credentials also need permission to retrieve the parameters and decrypt any SecureString values protected by KMS.
Step 2: Review the exported parameters
One reason I chose CSV rather than introducing another serialization format is that migration data is easy to inspect and edit.
Before writing anything into the target account simply open it in your preferred editor.
This is also a convenient point to modify values that should be different in the destination environment.
For example, the source account might contain:
/my-app/api/endpoint,https://old.example.com,String,False
while the new environment should use:
/my-app/api/endpoint,https://new.example.com,String,False
Just change it before importing.
This makes the tool useful not only for exact account migrations, but also for creating one environment based on another.
Step 3: Dry-run the destination import
Before actually writing parameters into the destination account, run:
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file my-app.csv \
--dry-run
--dry-run shows what would be written without making changes to Parameter Store.
I strongly recommend treating this as part of the normal migration procedure rather than as an optional debugging feature.
Step 4: Import into the destination account
Once the dry run looks correct:
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file my-app.csv
The tool creates parameters that don't exist and updates parameters that already exist using Overwrite=True.
The original String, StringList, or SecureString type from the CSV is preserved.
So a complete migration can be as simple as:
# Export from the source account
ssm-bulk pull /my-app/ \
--profile old-account \
--region us-east-1 \
--file my-app.csv
# Preview the destination import
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file my-app.csv \
--dry-run
# Import into the destination account
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file my-app.csv
That's essentially the entire account-to-account migration.
What happens to SecureString encryption?
The CSV contains the decrypted value and parameter type. It does not contain the original KMS ciphertext or migrate the source KMS key.
When the parameter is recreated as a SecureString in the destination account, it is encrypted again there.
In other words, this is a migration of the parameter's logical value, not a migration of its original encrypted representation.
This is worth considering if your organization requires SecureString parameters to use a particular customer-managed KMS key.
Migrating between AWS regions
Because both pull and push accept --region, the source and destination don't have to use the same AWS region.
For example, export from eu-west-1:
ssm-bulk pull /my-app/ \
--profile old-account \
--region eu-west-1 \
--file my-app.csv
and import into us-east-1:
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file my-app.csv
This lets the same tool handle both account and region migrations.
Keeping destination-specific parameters
An exact copy isn't always what you want.
For example, the destination account may already contain /my-app/database/password, and that password should remain different from the source environment.
ssm-bulk provides a merge operation for cases like this.
The CSV contains a keep field. If a parameter in the base file is True its current value and type are preserved during the merge rather than being overwritten by the changes file.
First, export the destination:
ssm-bulk pull /my-app/ \
--profile new-account \
--region us-east-1 \
--file destination.csv
Then export the source:
ssm-bulk pull /my-app/ \
--profile old-account \
--region us-east-1 \
--file source.csv
In destination.csv, mark values that should remain destination-specific:
name,value,type,keep
/my-app/database/password,destination-secret,SecureString,True
Then merge the two files:
ssm-bulk merge destination.csv source.csv \
--output migration.csv
During a merge, parameters that exist only in the changes file are added. Parameters appearing in both files are updated unless the base version has keep=True. Parameters existing only in the base file are dropped from the resulting output.
Finally, preview and apply the result:
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file migration.csv \
--dry-run
and then:
ssm-bulk push \
--profile new-account \
--region us-east-1 \
--file migration.csv
This is particularly useful when promoting configuration between development, staging, and production environments where most parameters should be synchronized but some values must remain environment-specific.
Push is not synchronization
There is another behavior worth understanding.
push creates or updates parameters. It does not delete parameters from the destination account.
For example, suppose the source contains:
/my-app/a/my-app/b
while the destination contains:
/my-app/a/my-app/b/my-app/legacy
After importing the source, /my-app/legacy will still exist.
For account migrations, I consider that a safer default than automatically deleting parameters that aren't present in the source.
If strict reconciliation is required, deletion should be handled as a separate and explicit operation.
Verifying the migration
After the push, you can export the destination again:
ssm-bulk pull /my-app/ \
--profile new-account \
--region us-east-1 \
--file verify.csv
The tool writes exported parameters sorted by name, which makes comparing the source and destination exports straightforward.
For example:
diff my-app.csv verify.csv
Once you're satisfied with the result, remove temporary files containing decrypted secrets:
rm my-app.csv verify.csv
Recommended migration process
For most migrations, my process is:
- Configure AWS credentials for the source and destination accounts.
- Export the source SSM hierarchy with
ssm-bulk pull. - Review the generated CSV.
- Modify environment-specific values if necessary.
- Use
ssm-bulk mergeif some destination-specific parameters must be preserved. - Preview the import with
ssm-bulk push --dry-run. - Import the parameters with
ssm-bulk push. - Export the destination again and verify the result.
- Remove temporary CSV files containing decrypted secrets.
The tool intentionally doesn't try to become a configuration-management platform.
It solves a much smaller problem: take an SSM Parameter Store hierarchy, make it inspectable, and move it somewhere else.
For occasional AWS account migrations, environment bootstrap, or configuration promotion, that's often all I need.
Source code: https://github.com/ilich/aws-samples/tree/main/ssm/bulk_parameters