A Claude Code B2B app with no way to deploy it twice the same way
The code was good. The path from a commit to production was a forty-minute manual ritual on one founder's laptop, with no staging, no rollback, and no test covering the three rules the product is sold on.Composite engagement. Details anonymised; figures are representative.
40 min → 6 min
commit to production, unattended
- Platform
- Claude Code
- Industry
- B2B SaaS · Compliance reporting
- Duration
- 3 weeks
- Findings
- 2 critical, 3 high, 2 medium
What we walked into
A two-person team selling compliance reporting to mid-size manufacturers. Nine paying customers on annual contracts, real revenue, and a product their buyers depend on for an audit trail they have to produce on request.
Deploying meant building locally, copying files to a virtual machine, connecting over SSH, restarting the service, and applying schema changes by hand.
There was no rollback. The previous build was whichever copy happened to still be in a directory called backup-old.
Every engagement opens with a free consultation and a scoped audit. This one drew on the following:
Composite engagement. Details anonymised; figures are representative.
What we found
Severity is about consequence, not effort. A critical finding is one where a stranger can read data, move money, or destroy state.
- Critical
No repeatable path to production
Production was a single virtual machine updated by copying files from a laptop.
- Critical
Migrations applied by hand, outside the repository
Schema changes were pasted into a database session on the server.
- High
Tests that assert the happy path
The suite had 190 passing tests, all describing what the code already did.
- High
No staging environment
Changes went from a developer's machine to nine paying customers. Integration testing happened in production, on customers who had bought a compliance tool specifically because they wanted fewer surprises.
- High
Live secrets committed to the repository
An environment file was committed in the second week and never removed.
- Medium
Unpinned and unreviewed dependencies
The lockfile was in .gitignore, so no two installs were guaranteed to match.
- Medium
No error tracking
Failures were discovered when a customer emailed. The app wrote unstructured logs to a file on the machine that had not been rotated in eight months and was the largest thing on the disk.
The work we did
Delivered over 3 weeks, on branches, alongside a founder who kept shipping product the whole time.
- 01
Containerised the app and captured the runtime in a Dockerfile, so the environment stops being a story about one particular machine.
- 02
Reconciled the two undocumented production migrations back into the migration history, then made migrations a gated, reversible step of the deploy rather than a manual afterthought.
- 03
Built a pipeline: install, typecheck, test, build, deploy to staging on every branch, deploy to production on a tagged merge, with the previous image one command away.
- 04
Stood up a staging environment carrying anonymised production data, so integration problems surface before customers meet them.
- 05
Wrote 74 tests around the three invariants the business depends on - tenant isolation on every report query, soft-delete exclusion in exports, and entitlement checks when a payment fails - including the negative cases, which are the ones that matter.
- 06
Removed the committed secrets, rotated every one of them, rewrote the history, and moved the values into a secret store with separate values per environment.
- 07
Committed the lockfile, replaced the two dependencies carrying advisories, dropped four that were not imported anywhere, and turned on automated advisory alerts.
- 08
Added error tracking with release tagging, structured logging with retention, and uptime monitoring that alerts a shared channel rather than one inbox.
- 09
Rehearsed a bad deploy and a rollback with both founders - twice - and left them a one-page runbook that assumes it is 6pm and something is broken.
Before and after
The same metrics, measured the same way on both sides of the work. Where a number got worse, it is in the table.
Commit to production
40 min, manual6 min, unattendedThe same pipeline runs against staging on every branch.
Rollback
No path90 sPractised by both founders, not merely documented.
Deploys needing a same-day fix
1 in 41 in 22Measured across the three months following handover.
Tests covering business invariants
074Tenant isolation, soft-delete in exports, entitlement on payment failure.
Live secrets in the repository
30Rotated, removed from history, and scoped per environment.
Environments
Production onlyLocal, staging, productionStaging is built from the same image and carries anonymised data.
Monthly infrastructure cost
$140/mo$265/moA staging environment, CI minutes, error tracking, and log retention. Less than one hour of support time a month.
Composite engagement. Details anonymised; figures are representative. Figures are rounded from what we measured across the engagements this study is drawn from.
What it runs on now
The builder stayed in the picture where it earned its place. Nothing here was rewritten for the sake of rewriting it.
- Claude Code
- TypeScript
- Next.js
- PostgreSQL
- Prisma
- Docker
- GitHub Actions
- Fly.io
- Sentry
- Vitest + Playwright
We were never scared of the code. We were scared of Fridays.
Other write-ups
Different builder, same shape of problem.
- Bubble
A Bubble booking app whose platform bill grew faster than its revenue
$2,300 → $640 - monthly platform bill, same traffic
Read the write-up - Lovable
A Lovable client portal where every tenant could read every other tenant
11 → 0 - tables readable across tenants
Read the write-up - Replit
An internal ops tool that lost a week of data every time it redeployed
96.8% → 99.95% - measured uptime after the move
Read the write-up
Same builder, same symptoms?
Send us a repo link and a paragraph about what it does. The audit is free and the report is yours either way.