Secrets Manager and Parameter Store
coreintermediateSecrets Manager stores credentials — database passwords, API keys, OAuth tokens — and can rotate them on a schedule automatically. Parameter Store (part of Systems Manager) stores configuration data — AMI IDs, endpoint URLs, feature flags — as plain `String`/`StringList` values or KMS-encrypted `SecureString` values, but has no automatic rotation. A secret hard-coded in source code, baked into a Docker image, baked into an AMI, or dropped in a plain `.env` file sits wherever that artifact goes — copied, logged, or leaked with it. Both services replace that hard-coded value with a runtime call: the application asks for the secret when it starts, instead of shipping the secret inside it.
Think of it as
A hard-coded secret travels with the artifact that contains it — the Git history, the Docker image layer, the AMI snapshot — forever, even after you rotate the real credential, because nothing forces old copies to update. Secrets Manager and Parameter Store both break that link: the artifact ships a reference (an ARN or a parameter name, itself not secret), and the actual value is fetched at runtime from a service that can change the value later without touching the artifact at all. Secrets Manager adds one more piece on top: an automatic rotation schedule that changes the credential AND its stored value together, on its own, so a leaked credential has a shorter useful life even if nobody notices the leak.
What we're doing: Retrieve a database password from Secrets Manager at application startup, instead of reading it from an environment variable set by a plain .env file.
- 1
- boto3 is the AWS SDK — the same call works from a Lambda function, an ECS task, or an EC2 instance, as long as its IAM role/profile is allowed to call GetSecretValue on this secret's ARN.
- 2
- json is needed because Secrets Manager stores the whole secret as one string — a JSON blob here holding {"username": ..., "password": ...} together, a common pattern for DB credentials.
- 6
- get_secret_value() is the runtime call that replaces a hard-coded value — nothing about the secret's actual value exists in this file, the Docker image built from it, or Git history.
- 7
- SecretString holds the JSON payload; a binary secret would come back in SecretBinary instead.
Why this works: The application source, the Docker image built from it, and the Git history behind it all contain zero copies of the actual password — only a resource identifier (the SecretId) and a runtime call. Rotating the real credential later means updating it once, in Secrets Manager, with no code change and no redeploy.
Baking a database password into a Docker image via an ENV instruction or a checked-in .env file
Wrong
Better
What you see: Anyone who can pull or inspect the image (docker history, docker inspect, or a registry with weak access controls) reads the password in plain text — and rotating the real credential does nothing to the copy already baked into every image already built and pushed.
Why: An image layer is immutable and gets distributed independently of the running container's lifecycle — a secret written into a layer is now part of that layer's content forever, extractable from any copy of the image, regardless of what the actual credential is changed to afterward.
- Secrets Manager
- Purpose-built for credentials — DB passwords, API keys, OAuth tokens
- Automatic rotation (Lambda function or native DB integration)
- Versioned with staging labels as part of rotation
- Billed per secret per month, plus per API call
- Parameter Store
- General configuration data — AMI IDs, endpoints, feature flags
- No automatic rotation — build your own if a SecureString needs one
- Plain String/StringList, or KMS-encrypted SecureString
- Standard tier free; advanced tier and high throughput billed
Remember: Secrets Manager: credentials, automatic rotation, versioned via staging labels. Parameter Store: general config, String/StringList/SecureString, no automatic rotation, keeps 100 versions free. Neither belongs in an AMI, source code, a Docker image layer, or a plain .env file — all four ship the secret with the artifact instead of fetching it at runtime. Scope IAM access to specific secret ARNs, never Resource: "*".

