#Dev Log#CareReady#AWS#CDK#CI/CD
Automating CDK Deploys Without Giving Up the Human Gate
CareReady's frontend deploys itself; the backend — Lambda, API Gateway, a RETAIN'd DynamoDB table — did not. Instead of adding auto-deploy, we split the pipeline by event type: push shows the diff, a manual dispatch does the deploy.
CareReady’s frontend deploys itself: push to main, GitHub Actions syncs to S3 and invalidates CloudFront. The backend — a CDK stack with Lambda, API Gateway, and a DynamoDB table marked RETAIN — did not. Our rule was explicit: cdk deploy only after a human confirms. The table holds real data. Infrastructure changes can be destructive in ways a static-site sync never is.
Then we wanted to add an endpoint, and doing cdk deploy by hand each time felt like exactly the manual toil CI is supposed to remove. The tension is real: automate the mechanics, but don’t let infrastructure ship itself.
The answer wasn’t “add auto-deploy.” It was to split the pipeline by event type.
Push previews, dispatch deploys
on:
push:
branches: [main]
paths: ['backend/**'] # backend changed → show me the blast radius
workflow_dispatch: # a human clicked "Run workflow" → allowed to deploy
jobs:
preview: # always runs
steps:
- run: npx aws-cdk@2 synth
- run: npx aws-cdk@2 diff || true
deploy: # only on the manual trigger
if: github.event_name == 'workflow_dispatch'
needs: preview
environment: production-backend # optional second gate (required reviewers)
steps:
- run: npx aws-cdk@2 deploy --require-approval never
Two properties do the work:
- Push never deploys. A backend change on
mainrunssynth+diffand stops. You get a rendered “here’s what would change” without anything changing. That preview is the safety net — most “oops” moments are visible in a diff before they’re real. - Deploy requires a deliberate human action.
workflow_dispatchmeans someone opened Actions and pressed the button. That is the confirmation the rule asked for — now automated end-to-end, no laptop, no local credentials, no half-configured CLI. Wire anenvironmentwith required reviewers and you get a second approval on top.
The credentials already existed for the frontend deploy. The interesting discovery: we’d assumed those keys were scoped to S3 and CloudFront and would need widening for CDK. They weren’t — the first backend deploy through the pipeline just worked. Worth checking before you invent a permissions project that doesn’t need to exist.
The Docker question that wasn’t
The new endpoint’s companion — a scheduled sender — needed a native Python dependency, which means CDK asset bundling, which means Docker. “I only have Docker on another machine” sounds like a blocker.
It isn’t, if your deploy runs in CI. GitHub’s Ubuntu runners ship with Docker. CDK’s bundling spins up its container there, produces Linux-correct wheels (which is the whole reason to use Docker over a local pip install on macOS), and bakes the asset — no local Docker, no matter what your laptop has. The bundling step ran clean in synth on the first try.
Why the split beats “just automate it”
You could make backend deploys fully automatic on push. It’d be less code. It would also mean a typo in a stack definition, merged in a hurry, reshapes production infrastructure with no one looking. For a static site, you roll back. For a table with RETAIN and a Cognito pool, “roll back” is a sentence you don’t want to say out loud.
Full automation isn’t the goal. Removing the toil while keeping the pause is. The push preview removes the guesswork; the manual dispatch keeps the pause. You get CI’s convenience and your own rule, intact.
Takeaway: automate the boring parts of a risky deploy, and make the deploy itself a button a human has to press.
This is part of building CareReady, a released web app at VEAI LAB. The implementation and CDK stack are public in the CareReady repository; my broader technical-PM profile is at hire-veai.com.

Thanks for reading — built with care, for caregivers.