Managing secrets and configuration centrally with AWS Secrets Manager and SSM Parameter Store: storing secrets and parameters, reading them from Lambda and ECS, plus rotation, tagging, and permission scenarios.

In episode 10 we defined the entire infrastructure as code. But infrastructure without configuration is like a car without keys: database URLs, API keys, passwords, and SMTP credentials have to go somewhere. Hardcoding them in code or environment variables is practical, but leaks through git history and logs. AWS answers with two centralized services: Secrets Manager for high-value secrets and SSM Parameter Store for general configuration.
In this episode you'll store and read secrets and parameters in LocalStack, connect them to Lambda and ECS, and test rotation and permission scenarios without a real AWS account.
Both services look similar, but they were created for different needs:
| Aspect | Secrets Manager | Parameter Store |
|---|---|---|
| Purpose | High-value secrets | General configuration |
| Automatic rotation | Yes (via Lambda) | No |
| Versioning | Stage-based (AWSCURRENT) | Versions, but simpler |
| Maximum size | 64 KB | 4,096 (Standard) / 8,192 (Advanced) characters |
| Price | Per secret + per call | Free (Standard) |
In the emulator, pricing rules don't apply — both are free and unlimited. What matters is getting used to choosing the right tool: store passwords, API keys, and tokens in Secrets Manager; store configuration values like log level or endpoint in Parameter Store.
The Secrets Manager API is very simple. Create a secret with --secret-string, then read its value:
awslocal secretsmanager create-secret \
--name db/password --secret-string 'P@ssw0rd!'
awslocal secretsmanager get-secret-value \
--secret-id db/passwordLocalStack stores that value in emulator state and returns it exactly like real AWS. To see the full JSON payload, pipe the output through jq:
awslocal secretsmanager get-secret-value \
--secret-id db/password | jq -r '.SecretString'Secrets have versions. When the value changes, the old version moves to the AWSPREVIOUS stage and the new version becomes AWSCURRENT:
awslocal secretsmanager put-secret-value \
--secret-id db/password --secret-string 'P@ssw0rd!2'
awslocal secretsmanager get-secret-value \
--secret-id db/password --version-stage AWSPREVIOUSThis pattern enables fast rollback: applications always read AWSCURRENT, and teams can compare old versions without keeping manual duplicates.
SSM Parameter Store has three types: String, StringList, and SecureString (KMS-encrypted). Parameters also have tiers: Standard (free, 4,096 characters) and Advanced (paid, 8,192 characters):
awslocal ssm put-parameter \
--name /app/prod/LOG_LEVEL --value debug --type String
awslocal ssm put-parameter \
--name /app/prod/DB_PASSWORD --value 'secret-value' --type SecureString
awslocal ssm get-parameter --name /app/prod/DB_PASSWORD --with-decryptionWithout --with-decryption, a SecureString value appears encrypted. LocalStack uses the default KMS key alias/aws/ssm for this — just like AWS.
Naming parameters with a hierarchical path unlocks the get-parameters-by-path feature — fetch an entire service's configuration in a single call:
awslocal ssm get-parameters-by-path \
--path /app/prod --recursiveA secret is only useful if applications can read it. In LocalStack, Lambda code reads secrets exactly as in production — just point the SDK at the emulator endpoint:
import boto3
def handler(event, context):
client = boto3.client(
"secretsmanager",
region_name="us-east-1",
endpoint_url="http://localhost:4566",
aws_access_key_id="test",
aws_secret_access_key="test",
)
secret = client.get_secret_value(SecretId="db/password")
return {"password": secret["SecretString"]}For ECS, secret values are referenced directly in the task definition via the secrets property pointing to an ARN — the container runtime pulls them itself. The same pattern applies in production, so behavior never drifts.
Real rotation on AWS uses a Lambda that writes a new value to the secret. LocalStack emulates the rotate-secret API: the call is accepted and completes, but without running a real rotation Lambda:
awslocal secretsmanager rotate-secret \
--secret-id db/passwordNote
Rotation in LocalStack is a mock — enough to test code that calls the rotation API, but not to verify the rotation Lambda logic itself. Test rotation logic in CI with real test secrets.
Plenty of secrets and parameters need organization. Both services support tagging for audit and cost allocation:
awslocal secretsmanager tag-resource \
--secret-id db/password --tags 'Key=env,Value=prod'
awslocal secretsmanager list-tags-for-resource \
--secret-id db/passwordFor SSM, use ssm add-tags-to-resource with --resource-type "Parameter" and the parameter ARN as --resource-id.
On real AWS, access to secrets is governed by IAM policies. In LocalStack, IAM evaluation is much looser: Deny permissions are generally ignored and almost every call succeeds as long as the ARN format is correct. That means:
Summary of this episode:
create-secret, put-secret-value, and the AWSCURRENT/AWSPREVIOUS version staging.String, SecureString, and path hierarchies with get-parameters-by-path.endpoint_url pointing to http://localhost:4566.Your configuration and secrets are now centralized. But there's a silent problem: restarting LocalStack wipes all the state you've built. In episode 12 we rescue that hard work with persistence and Cloud Pods — state that survives restarts and can be shared across your team. See you there!