Configure Apps with Parameter Store
Use Parameter Store for configuration in this hands-on AWS Cloud & DevOps with Python lesson — learn the core concept, walk through a practical exercise, and see how it connects to your next step.
Focus: use parameter store for configuration
You've built a Python app, containerized it, and pushed it to AWS — now you need to store the database URL, API keys, and feature flags without hardcoding them. Copy-pasting secrets into code or environment files is a fast track to leaked credentials and brittle deployments. AWS Systems Manager Parameter Store gives you a secure, versioned, serverless home for configuration, and it's dead simple to use from Python with boto3.
The problem this lesson solves
Configuration is the quiet killer of deployments. Hardcoded values in source code get committed to Git, leaked in screenshots, and mixed up across environments. Environment variables are better, but they're still scattered across EC2 user data, Lambda settings, and CI/CD panels — and every engineer has to ask "where is that set?"
Parameter Store centralizes configuration in one AWS service. Your Python code reads parameters at runtime, pulling the right value for the right environment (dev, staging, prod) with zero code changes. No more if ENV == 'prod': blocks checking environment variables. No more hunting through 40-line docker run commands for the Postgres password.
Parameter Store also gives you versioning, encryption, and IAM-based access control — features you'd have to build yourself if you rolled your own config file. And unlike a database, it costs nothing for standard parameters and doesn't require you to manage any infrastructure.
Core concept / mental model
Think of Parameter Store as a key-value vault in the cloud. Each parameter is a named entry that holds a string (or a structured value like JSON or a list). Your Python application becomes a client that asks the vault for values by name, just like a dictionary lookup.
A simple mental model:
- Store — a parameter like
myapp/database_urlis created in Parameter Store. - Fetch — your Python code calls
get_parameter()for that name. - Use — the returned value configures your app's database connection, third-party API, etc.
The name itself can create a hierarchy: /myapp/prod/db_url, /myapp/staging/db_url. This lets you fetch a whole tree of parameters with one call and keep environments isolated.
Pro tip: Treat parameter names like a filesystem path. Use a consistent prefix (e.g.,
/project/env/) so you can query by path and apply IAM policies to subsets.
Definitions
- Parameter: a named key-value pair, e.g.,
name: /myapp/db_url,value: postgresql://user:pass@host:5432/db - Standard parameter: free, unencrypted (or encrypted with an AWS-managed key), max 4KB value
- Advanced parameter: paid, up to 8KB, supports policies (e.g., expiration)
- Version: every update increments the version number; you can retrieve a specific version if needed
How it works step by step
Let's trace the journey of a configuration value from your AWS account to a Python variable:
- Create the parameter — You (or Terraform/CloudFormation) call
put_parameter()for each config value. You pick a name, a value, a type (String,StringList,SecureString), and an optional KMS key for encryption. - Attach IAM permissions — Your EC2 instance or Lambda role needs
ssm:GetParameter(orssm:GetParametersByPath) on the parameter's ARN. Without it, boto3 throwsAccessDeniedException. - Fetch from Python — Your app calls boto3's
get_parameter(). If the value is aSecureString, boto3 automatically decrypts it (provided the role haskms:Decrypton that key). - Cache and use — Since API calls cost time and can hit throttling limits, best practice is to fetch once at startup and cache the value.
The cause-and-effect is simple: the IAM policy allows the call, the SDK reads the parameter, and the returned string configures your app.
IAM policy requirement
A minimal policy for reading one parameter:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/myapp/*"
},
{
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "*"
}
]
}
Pro tip: Scope the resource to a path prefix (like
myapp/*) so your app can't read all parameters — least privilege is a habit worth building.
Hands-on walkthrough
Let's build a practical example: a Python app that reads its database URL from Parameter Store.
Step 1: Create the parameter
Use the AWS CLI to create a SecureString for the database URL:
aws ssm put-parameter \
--name "/myapp/prod/db_url" \
--value "postgresql://admin:supersecret@prod-db.example.com:5432/mydb" \
--type SecureString
To update it later, add --overwrite:
aws ssm put-parameter --name "/myapp/db_url" --value "new-url" --type String --overwrite
Step 2: Read it from Python
Now the fun part — a complete Python script that fetches and uses the parameter:
import boto3
import json
ssm = boto3.client('ssm', region_name='us-east-1')
def get_parameter(name, with_decryption=True):
"""Fetch a parameter value, optionally decrypting SecureString."""
response = ssm.get_parameter(
Name=name,
WithDecryption=with_decryption
)
return response['Parameter']['Value']
db_url = get_parameter('/myapp/prod/db_url')
print(f"Connecting to: {db_url}")
Expected output (if you print the URL without masking — don't do this in production!):
Connecting to: postgresql://admin:supersecret@prod-db.example.com:5432/mydb
Step 3: Fetch multiple parameters by path
For apps with several config values, query a whole path:
import boto3
ssm = boto3.client('ssm', region_name='us-east-1')
def get_parameters_by_path(path):
"""Return a dict of all parameters under the path."""
parameters = {}
paginator = ssm.get_paginator('get_parameters_by_path')
for page in paginator.paginate(Path=path, Recursive=True, WithDecryption=True):
for p in page['Parameters']:
# Strip the path prefix to use as a simple key
key = p['Name'].split('/')[-1]
parameters[key] = p['Value']
return parameters
config = get_parameters_by_path('/myapp/prod')
print(config['db_url'])
print(config['api_key'])
Step 4: Cache the values
To avoid hitting API limits and to speed up startup, cache the config in a global dict:
import boto3
from functools import lru_cache
ssm = boto3.client('ssm', region_name='us-east-1')
@lru_cache(maxsize=1)
def load_config():
"""Load all parameters once and cache."""
params = {}
paginator = ssm.get_paginator('get_parameters_by_path')
for page in paginator.paginate(Path='/myapp/prod', WithDecryption=True):
for p in page['Parameters']:
params[p['Name']] = p['Value']
return params
# Use it
config = load_config()
print(config['/myapp/prod/db_url'])
# Subsequent calls hit the cache, not the API
Pro tip: For Lambda, cache outside the handler function to reuse across warm starts. For EC2, cache at process start.
Compare options / when to choose what
Parameter Store isn't the only way to manage config on AWS. Here's how it stacks up:
| Feature | Parameter Store | Secrets Manager | Environment Variables |
|---|---|---|---|
| Cost | Free (standard) | $0.40/secret/month | Free |
| Max size | 4KB (standard), 8KB (advanced) | 64KB | ~4KB per env var |
| Rotation | Manual or via Lambda | Built-in scheduled rotation | N/A |
| Encryption | Optional (SecureString) | Automatic | Depends on platform |
| Versioning | Yes | Yes | No |
| IAM policies | Fine-grained per path | Full ARN access | Limited |
| Best for | Configs, simple secrets | Rotating credentials (DB passwords, API keys) | Short-lived or ephemeral values |
Choose Parameter Store when you need a cheap, central config hub with path-based hierarchy. Choose Secrets Manager when you need automatic rotation or larger secrets. Use environment variables only for non-sensitive, per-process settings like DEBUG=true.
Troubleshooting & edge cases
Let's tackle the most common problems you'll hit.
1. AccessDeniedException
Error: An error occurred (AccessDeniedException) when calling the GetParameter operation
Cause: The IAM role attached to your EC2/Lambda doesn't have ssm:GetParameter, or the resource ARN doesn't match.
Fix: Attach the policy we created earlier. Verify the parameter ARN format: arn:aws:ssm:region:account-id:parameter/path/name. Note the parameter part is singular.
2. ParameterNotFound
Error: ParameterNotFound when calling get_parameter
Cause: The name doesn't exist, or you're in the wrong region or account.
Fix: Double-check the name including the leading slash (e.g., /myapp/db_url). Use the AWS Console to confirm the parameter exists in the same region as your Python code.
3. ThrottlingException
Error: ThrottlingException: Rate exceeded
Cause: You're calling get_parameter inside a hot loop or without caching.
Fix: Implement caching as shown above. If you need frequent reads, use GetParameters to batch multiple names in one call, or use a local cache library like cachetools.
4. Decryption failure for SecureString
Error: KMSException or IncorrectKeyException when calling get_parameter with WithDecryption=True
Cause: Your IAM role doesn't have kms:Decrypt on the KMS key that encrypted the parameter.
Fix: Attach a KMS policy that allows kms:Decrypt on the specific key ARN. If you used the default alias/aws/ssm, the AWS-managed key is used — ensure your role has decrypt on that key.
5. Wrong value returned in production
Symptom: Your app uses a staging URL in production.
Cause: The parameter path is hardcoded to /myapp/staging/.
Fix: Use an environment variable or a profile string to select the environment:
import os
env = os.getenv('APP_ENV', 'dev')
params = get_parameters_by_path(f'/myapp/{env}')
Now you switch environments by changing APP_ENV, not by editing code.
What you learned & what's next
You now understand how to use Parameter Store for configuration: create parameters, read them securely from Python, and manage them via IAM and path hierarchies. You can handle secrets without hardcoding, version your config, and switch environments cleanly. This is a foundational skill for any AWS deployment.
Next in your AWS Cloud & DevOps with Python journey, you'll learn how to manage infrastructure as code — using Terraform or CloudFormation to define all your resources, including these Parameter Store entries, so your entire stack is reproducible and reviewable.
Keep building — you're one step closer to deploying robust, professional Python apps on AWS.
Practice recap
Now try it yourself: create a parameter for a fake API key (SecureString), write a Python script that reads it and prints a masked version (e.g., sk-****1234), then update the parameter value and verify your script picks up the change. This cements the read-update cycle you'll use daily in production.
Common mistakes
- Hardcoding parameter names in the app instead of using environment variables to select the environment — this locks you to one env and forces code changes.
- Calling get_parameter on every request without caching, causing throttling and slow startup.
- Using plain String type for secrets instead of SecureString, leaving sensitive data unencrypted in AWS.
- Forgetting to attach kms:Decrypt permission for SecureString parameters, causing mysterious decryption errors.
- Putting the full parameter ARN in the IAM policy without the
parameterkeyword — the ARN must bearn:aws:ssm:region:acct:parameter/...
Variations
- Use
get_parametersto fetch a batch of specified names in one call, reducing API round-trips. - Use advanced parameters when you need values larger than 4KB or parameter policies like expiration.
- Use AWS Secrets Manager instead of Parameter Store for credentials that require automatic rotation.
Real-world use cases
- A Django or Flask app on EC2 reads its database URL and secret key from Parameter Store at startup, enabling environment-specific configs without code changes.
- A Lambda function fetches feature-flag JSON from Parameter Store to toggle behavior in production without redeploying.
- A CI/CD pipeline stores build configuration values (e.g., artifact bucket, deploy region) in Parameter Store and reads them during deployment scripts.
Key takeaways
- Parameter Store centralizes configuration as versioned key-value pairs, removing hardcoded values from code.
- Use SecureString for secrets and String for non-sensitive config; WithDecryption=True in boto3 handles decryption automatically.
- IAM permissions are mandatory — grant
ssm:GetParameterandkms:Decryptto your compute role. - Leverage path hierarchies (
/app/env/param) to organize and fetch related parameters in one call. - Cache parameter values to avoid throttling and improve app performance.
- Parameter Store is free for standard parameters, making it a cost-effective choice for most config needs.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.