How a Tiny Team Ships Like a Big One
The boring rails: how code gets from a laptop to prod here
Path-filtered CI, a release train that cuts itself, migrations as first-class deploy steps, and guardrails with scar tissue - the dull pipeline that makes agent velocity safe.
On July 2 we promoted to production twice before the end of the day - 46 commits, 5 database migrations, features from four engineers and two fixes authored by agents. Both promotes were green. Nobody stayed late. The second one had a broken migration in it, and I'll get to why that didn't matter.
Most of what people ask us about is the AI: the SRE fleet, the review bots, the fact that agents write our code. But the honest answer to "how do you let agents ship that fast?" has no AI in it at all. It's the pipeline. Once agents started writing the code, the rails became the thing standing between us and a bad week - so ours are deliberately strict and deliberately dull.
Deploys used to be events
A year ago, deploys here were manual, nervous events. Someone with the right terminal ran the right commands in the right order and watched. Every deploy consumed a senior engineer's attention for an afternoon, so we batched changes, which made each deploy bigger, which made it scarier, which made us batch more. That spiral is survivable when humans write a few PRs a week, and it stops being survivable once agents start opening fixes around the clock.
What we built
The shape is a train, and the train is boring on purpose.
- Merges land on main; beta deploys automatically. Every merge - human or agent, all through the review gate - goes straight to the beta environment. Beta is where "it works on my machine" goes to be disproven.
- Main cuts itself to staging. An hourly tick promotes main to staging whenever main is ahead of it and main's CI is green. No human starts this. Staging runs the same infrastructure shape as production, and what lands there soaks under real usage from the whole team.
- Staging is promoted to release by pull request, whenever the gate is satisfied. There is no release day and no window to wait for. The bar is thirty minutes of soak, a green staging canary, and no active staging alarms; clear it and the release can go, which in practice means we ship most days. This is the part I care most about: the release itself is a PR. A human reads the preview, sees every commit and every migration riding along, and merges. Our release-preview script does a dry run of the whole thing first - think of it as a changeset for the release process. The release skill that assembles all of this explicitly refuses to auto-merge that PR. Deploying to prod is a decision, so we kept it shaped like one.
- CI only builds what changed. The monorepo fans out into about 15 independently deployable services; a path filter decides which of them a PR actually touched, and only those build and deploy - on self-hosted runners inside our own AWS account, so builds get native architecture, real IAM, and VPC access instead of a pile of credentials.
Then there are migrations, which get treated as what they are: the most dangerous thing we ship.
- Migrations run as one-off ECS tasks before the service deploys, not as a side effect of app startup. If a migration fails, the deploy stops and the pipeline dumps the migration logs into the run so nobody has to go spelunking.
- A static-analysis step reads every migration and flags destructive operations - dropped tables and columns, renamed tables, dropped indexes and constraints, truncations - before a human ever approves the release they ride in.
- We only roll forward. Rollback means pinning the previous service revision - code is easy to un-deploy. We never run downgrade migrations against production data; a "down" that was never tested against real data is not a rollback, it's a second incident.
The save that proves the shape
That second July 2 promote: one of its migrations had a NULL-binding bug that only surfaced against staging's data. The deploy check on the release PR went red, the release stopped, the fix was written and re-verified on staging, and the corrected release went out green the same afternoon. Total production impact: zero. Nobody was paged, because the gate did its job before prod, not after. Somewhere along the way we stopped trying to make mistakes impossible and started arranging for them to happen in the cheap place instead.
The same philosophy applies to infrastructure, where our guardrails are honest scar tissue. We once left an S3 bucket versioned with no lifecycle rule and quietly accumulated roughly 250 TB of dead object versions before anyone noticed the bill. Now a plan-time check reads every Terraform plan and blocks any versioned bucket that lacks an expiration rule. One incident, one permanent guardrail, and that class of mistake is retired. (We also don't ship to prod on Friday afternoon. Some guardrails are just calendars.)
Real numbers
- ~15 services, path-filtered - a one-line docs change builds nothing it doesn't have to.
- Two same-day green production promotes on July 2: 42 + 4 commits, 5 migrations applied.
- Zero broken code reaching production from that release - the broken migration died on the release PR, where it was supposed to.
- 30 minutes of staging soak, a green canary and no active alarms: the entire bar for shipping to production. Cut to prod runs about 35 to 40 minutes end to end, and hotfixes can bypass the queue through a severity rubric with their own approval path.
Where the humans sit
A human merges the release PR, after reading the preview. A human reads the destructive-migration warnings. A human decides hotfix severity. Everything else - building, filtering, migrating, log-dumping, soak-tracking - is machinery. The pattern is the same one from our SRE fleet: automate the toil completely, then make the remaining decisions impossible to take by accident.
Steal this
Make your release a pull request. It's the cheapest high-leverage change on this page: you get a human gate, an audit trail, a diff of exactly what's shipping, and a place for automation to attach its evidence - for free, using the review muscle your team already has. Then promote your migrations to first-class deploy steps with their own logs and their own static analysis, and adopt forward-fix-only before an untested downgrade teaches you why.
One more thing about those rails: every PR that rides them gets reviewed - and for four out of five of them, the reviewer isn't human. That's this track, next week. On Thursday the product track puts our own graders on trial: dueling judges.
This post is part of How a Tiny Team Ships Like a Big One, a series on how six builders run a production AI company. Building at Aithon - if this is how you want to work, talk to us.