Thinking
Technical articles on AI, development tools, cloud architecture, and software engineering.
I pay $200 a month for Claude. Here is what it would cost at API prices.
Anthropic filed a confidential S-1 on 1 June. Press reports point to a listing in October, not confirmed by the company. I am a solo contractor who does almo...
Infrastructure as code does not mean Terraform. CDK, Pulumi, and the question that actually decides it
Somewhere around 2019, "infrastructure as code" became a synonym for Terraform in most conversations, the way "search" means Google. It i...
What Claude Code wrote of our infrastructure, and what we refused to merge
We use Claude Code for infrastructure work daily, on a platform we run for a US client: CDK stacks, deploy scripts, IAM policies, alarms, runbooks. About a t...
Queues without Kafka: SQS, EventBridge, and the one place we actually needed a stream
Every platform reaches the moment where a request does too much. The checkout handler sends an email, updates the search index, notifies the warehouse, recor...
DynamoDB single-table or Postgres? How we chose, per workload, and where we got it wrong
The DynamoDB versus Postgres debate is usually argued as a religion. One side says relational data belongs in a relational database and single-table design i...
Docker images for Node in 2026: multi-stage, distroless, and the 1.1 GB image that became 140 MB
The first Dockerfile in most Node projects is eleven lines long, starts from node:20, copies the repository, runs npm install and npm run build, and ships. I...
A monorepo with three apps: how we build only what changed, and got the pipeline from eight minutes to three
Three Next.js applications, four shared packages, one repository. That's the shape of the platform we run for a US client, and it's the right shape: shared t...
Rate limiting: at the edge, in the gateway, or in the app? Three layers, three jobs
"Add rate limiting" is a one-line ticket that hides three different problems. Someone is hammering the login route from one IP: that's abuse, and y...
Health checks that lie: six ways your service says "ready" when it isn't
A health check is a contract between your application and the thing that routes traffic to it. The router asks "can you take a request?" and the ap...
Backups you've never restored aren't backups. A restore drill, timed.
Every AWS data store has backups turned on by default or by a checkbox. Aurora keeps automated backups. DynamoDB has point-in-time recovery. S3 has versionin...
An incident runbook for a team without on-call
Big companies have on-call rotations, incident commanders, severity matrices and a tooling budget to match. A team of three has a group chat and whoever's aw...
Rotating the database password without downtime: the thing you've been putting off
Everyone agrees the database password should rotate. Almost nobody's does, and the reason isn't laziness. It's that the first time you try, something breaks:...
WAF for a Next.js app: the managed rules that block legitimate traffic, and what we did about them
Turning on AWS WAF in front of a web application is one CDK construct and a checkbox's worth of managed rule groups. It's also, in our experience, guaranteed...
Secrets in CDK: Secrets Manager, Parameter Store, and never anything in the template
A CloudFormation template is a text file. It's stored by CloudFormation, it's in cdk.out on every developer's disk, it's in the CI logs from the last synth, ...
Reserved, Savings Plans, or nothing: when a one-year commitment is actually worth it at small scale
AWS will give you up to 72 % off compute if you promise to keep paying for it for three years. The number is real and it's in every cost-optimisation checkli...
NAT Gateway is the most expensive line you don't see
It isn't on the bill. Not as "NAT Gateway", anyway. It's under EC2-Other, a usage type called NatGateway-Hours next to another called NatGateway-By...
Zero-downtime deploys aren't a platform feature. They're a schema-migration discipline.
Every container platform will tell you it does zero-downtime deployments. App Runner, Fargate, Cloud Run, Kubernetes: they all start new instances, wait for ...
Dev, staging and prod from day one: why a team of three shouldn't wait
"We'll separate environments later, when we have users." We've heard that sentence from every small team we've worked with, and we've said it ourse...
You don't need Kubernetes. You need a good Dockerfile and a place to run it.
Every few months a team of three to five engineers asks us whether they should move to Kubernetes. The application is a web front-end, an API, a couple of wo...
What managed observability really costs at 100 GB a day: Datadog, Grafana Cloud, self-hosted
The observability bill is the one that surprises people twice. The first time is when it arrives and it's a third of the compute bill. The second is a year l...
Alerts that don't know which cloud they run in
Every cloud sells you alarms. CloudWatch Alarms, Azure Monitor alert rules, Cloud Monitoring alerting policies: each one is preconfigured, each one is a few ...
Your traces shouldn't know which cloud they run in either
We made the case that application logs shouldn't be owned by the cloud: the app emits, an agent stamps the cloud onto it, a neutral backend stores it. Logs w...
Same tag, no deploy: why cdk deploy with an unchanged image does nothing, and the 300-line script we wrote around it
Two stories about the same deploy pipeline, told together because the second exists because of the first. The first is a bug that isn't a bug: we rebuilt a c...
CloudWatch data protection policies: masking PII before it lands in your logs
A support engineer asked for a debug log on one API route, "just for a week", to chase a bug in address validation. The log line was the whole requ...
Aurora Serverless v2 after six months: the 0.5 ACU floor and the auto-pause myth
Aurora Serverless v2 is sold with one sentence: pay for the database capacity you use, scaled in fractions of a second. Six months of running it in productio...
The CloudFormation 500-resource limit, and how we split a stack without losing data
cdk deploy failed with a message we'd never seen: Template format error: Number of resources, 503, is greater than maximum allowed, 500. Nothing in the diff ...
GitHub OIDC instead of access keys: one deploy role per AWS account, zero secrets in CI
There is a long-lived AWS access key in your GitHub repository secrets. It was created by whoever set up the first pipeline, it has AdministratorAccess becau...
Build-time vs runtime config in Next.js on containers: the bug that took two deploys
We flipped a feature flag. The flag was an environment variable on the App Runner service, NEXT_PUBLIC_NEW_CHECKOUT=true, changed through CDK, deployed clean...
Preview environments per pull request on AWS, without Vercel
Code review has a ceiling. A reviewer can read a diff and reason about it, but they can't click on it. For a product with a UI, the most useful review commen...
The AWS bill of a three-person startup, line by line
Everyone publishes architecture diagrams. Nobody publishes the invoice. That's a shame, because the invoice is where the architecture becomes honest: the ser...