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.

Key APIs in Problem 1: reading config inside the handler The main identifiers used in Problem 1: reading config inside the handler KeyErrorhandler.pyhandler
The APIs and identifiers this section uses.

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.

Problem 2: putting secrets in Lambda environment variables The core point of Problem 2: putting secrets in Lambda environment variables putting secrets in Lambda environ…db_password = os.environ["DB_PASSWORD"] # visible in the console, stored at rest L…
The key point of this section at a glance.

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}
Key APIs in Secure implementation The main identifiers used in Secure implementation SecretStrBaseSettingsSettingsConfigDictSettingsSecretsManager
The APIs and identifiers this section uses.

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_cache plus module-level settings gives one validated object per container.
  • SecretStr keeps the password out of any print that CloudWatch would capture.
  • Avoid importing heavy SDK clients at module scope only to read config; construct boto3 clients lazily if cold-start latency matters.
Gotchas & version-specific behaviour Key points from Gotchas & version-specific behaviour Module-scope construction run…surface config errors there.Warm invocations reuse the mo…SecretStr keeps the password …Avoid importing heavy SDK cli…
The essentials of Gotchas & version-specific behaviour.

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 Duration and initialization errors to catch config failures on deploy.
Production parity checklist Key points from Production parity checklist Construct the settingsmodel …Keep only non-secret configi…Pull secrets from a managera…Add a CI test that importsth…Alarm on Init Duration andin…
Every item to tick off for Production parity checklist.

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.