Blog

All posts
John Damask · 2026-03-02
devlogdeploymenttestinginfrastructureaws

The project had a single deploy.sh that always targeted production. With a second AWS account set up for testing, there was nothing preventing an accidental production deploy from the wrong terminal session or the wrong credentials.

The fix splits the script into two standalone files: deploy-prod.sh and deploy-test.sh. Both add an account guard at the top -- the first thing each script does is call aws sts get-caller-identity, compare the returned account ID against the expected value, and exit immediately with a clear error message if they don't match. The profile is set via export AWS_PROFILE at the top of each script so every subsequent AWS CLI call picks it up automatically, rather than threading --profile through 30+ individual commands.

The test script required more thought than a simple rename. The prod deployment uses an existing public S3 bucket in the prod account as temporary PDF storage (the browser uploads PDFs there via presigned URL; the Lambda passes the URL to Claude, then deletes the PDF). That bucket lives in the prod account, so having the test account's Lambda generate presigned URLs for it would mean cross-account IAM complexity and the test deploys would be reconfiguring CORS on a prod bucket. Instead, deploy-test.sh creates its own PDF upload bucket in the test account on first run, applies a public-read policy (so Claude can fetch PDFs by URL), and configures CORS for the test domain. A separate deployment bucket holds Lambda artifacts. Both test buckets are created idempotently in the script itself, same pattern as the existing deployment bucket creation logic.

The test environment also needs its own domain. A subdomain was set up via a Route 53 hosted zone in the test account, with an NS delegation record added to the prod account's zone. DNS propagated within minutes. With that in place, deploy-test.sh can provision its own ACM certificate and complete the full CloudFormation deployment -- including CloudFront and Route 53 records -- entirely within the test account, without touching any prod infrastructure.