I ship mobile apps. AWS backends are not my home turf, and I expected standing up an API — stories in a database, publish with auth, public reads — to eat a week.
It ate an afternoon, and the reason is that the whole backend is two files. One YAML template (~60 lines) describes everything: the database table, the function, the HTTP front door. One Python file is the entire API. sam deploy reads the description and conjures the real infrastructure; change the file, deploy again, done. Nothing to patch, nothing idling at 3am, and at my scale the bill is literally pennies — pay-per-request everywhere.
The only wall of the day was, in retrospect, the best part. I stored an auth token in SSM Parameter Store and naturally marked it SecureString — encrypted, the responsible choice. Deploy failed: "Non-secure ssm prefix was used for secure parameter." CloudFormation refuses to inject encrypted parameters into Lambda environment variables — because env vars sit in plaintext in the console, and injecting a decrypted secret there would quietly defeat the encryption. The wall was a security feature protecting me from myself.
Two honest exits, and we ended up using both: a plain String parameter for the low-stakes token, and for the real secret (an OAuth client secret), reading it at runtime from inside the function with a narrowly-scoped IAM permission. Annoying in the moment. Correct forever after.
Reproduce this
Prerequisites: an AWS account, SAM CLI installed, Python 3.12.
Your agent will need from you: the resource names you want, your region, and which values are secrets (they decide String vs runtime-fetched SecureString).
Steps: write template.yaml with AWS::Serverless::Function + AWS::Serverless::HttpApi + AWS::DynamoDB::Table (PAY_PER_REQUEST); one handler file routing on rawPath; deploy with sam deploy --resolve-s3 --capabilities CAPABILITY_IAM. Plain config via '{{resolve:ssm:...}}' in env vars; secrets via boto3 get_parameter(WithDecryption=True) at runtime plus SSMParameterReadPolicy.
Verify: curl every route; then break it on purpose — mark a param SecureString and watch the deploy fail with the exact error above, so you recognize it forever.