Configuration for AWS Lambda Python Handlers
A Lambda handler has an unusual lifecycle: module-level code runs once per cold start and is reused across warm invocations. Reading configuration in the wrong place either re-parses it on every request or defers a fatal error until the first real event. This page shows where to read and validate config, building on the os.environ typing rules and pydantic-settings validation.
Problem 1: reading config inside the handler
# handler.py — ANTI-PATTERN
import os
def handler(event, context):
table = os.environ["TABLE_NAME"] # re-read every invocation; KeyError mid-request
...
A missing TABLE_NAME here fails on the first event, not at deploy time, and the read repeats on every warm invocation.
Problem 2: putting secrets in Lambda environment variables
# ANTI-PATTERN: DB_PASSWORD set as a plain Lambda env var
db_password = os.environ["DB_PASSWORD"] # visible in the console, stored at rest
Lambda environment variables are readable by anyone with configuration access. Secrets belong in AWS Secrets Manager or Parameter Store.
Secure implementation
Validate configuration at module scope so a cold start fails fast, and fetch secrets from a manager.
# config.py — imported once per cold start
from functools import lru_cache
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(extra="forbid")
table_name: str
db_password: SecretStr # sourced from Secrets Manager env-injection or SDK
@lru_cache
def get_settings() -> Settings:
return Settings()
settings = get_settings() # constructed at import; a bad value fails the cold start
# handler.py
from config import settings
def handler(event, context):
# settings is already validated; no per-invocation parsing
return {"table": settings.table_name}
Gotchas & version-specific behaviour
- Module-scope construction runs during the init phase, which has a longer timeout than the handler — surface config errors there.
- Warm invocations reuse the module globals, so
lru_cacheplus module-levelsettingsgives one validated object per container. SecretStrkeeps the password out of anyprintthat CloudWatch would capture.- Avoid importing heavy SDK clients at module scope only to read config; construct
boto3clients lazily if cold-start latency matters.
Production parity checklist
- Construct the settings model at module import, not inside the handler.
- Keep only non-secret config in Lambda environment variables.
- Pull secrets from a manager and wrap them in
SecretStr. - Add a CI test that imports the handler module with a staging-shaped environment.
- Alarm on
Init Durationand initialization errors to catch config failures on deploy.
Frequently asked questions
Where should a Lambda handler read its configuration?
At module scope, outside the handler function, so it is validated once per cold start and reused across warm invocations. Reading inside the handler re-parses on every request and hides configuration errors until traffic arrives.
Are Lambda environment variables a safe place for secrets?
They are visible in the console and to anyone with lambda:GetFunctionConfiguration, and are stored at rest. Use them for non-secret config; pull real secrets from AWS Secrets Manager or Parameter Store at cold start instead.
How do I make a Lambda fail fast on bad configuration?
Construct a pydantic BaseSettings model at module import. If a variable is missing or malformed, the import raises and the Lambda reports an initialization error immediately.
Key takeaways
Validate Lambda configuration once at module scope so a cold start fails fast, keep secrets in a manager rather than environment variables, and reuse the validated object across warm invocations.