Skip to content
Case study

An internal ops tool that lost a week of data every time it redeployed

A warehouse scheduling tool ran on a framework development server with its SQLite file on the container disk.Composite engagement. Details anonymised; figures are representative.

Headline result

96.8% → 99.95%

measured uptime after the move

Platform
Replit
Industry
Logistics · Internal tooling
Duration
2 weeks
Findings
2 critical, 2 high, 2 medium
The situation

What we walked into

An operations manager built the tool in Replit to replace a shared spreadsheet that had stopped coping.

Twice in six weeks the team arrived to find the previous week's bookings missing. Both times it was written off as someone deleting rows by accident, and both times a person was spoken to about it.

The same audit found the app serving traffic from the framework's development server with the interactive debugger switched on, which meant any unhandled exception offered a stack trace and a Python console to whoever triggered it.

Engagement

Every engagement opens with a free consultation and a scoped audit. This one drew on the following:

Composite engagement. Details anonymised; figures are representative.

Production check

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.

2 Critical2 High2 Medium
  • Critical

    User data on an ephemeral container disk

    app.db was written to the working directory. Redeploys, idle restarts, and platform maintenance all replace that filesystem.

  • Critical

    Development server facing the internet

    The app ran under the framework's dev command with debug mode enabled - a single-threaded server with an interactive debugger attached.

  • High

    Credentials hard-coded in source

    The SMTP password and the carrier API token were string literals in a config module, committed since the first week.

  • High

    Two admin routes with no authentication

    The export and reset endpoints were reachable without a session. The links to them were only rendered for admin users, which is not the same thing as protecting them.

  • Medium

    Cold starts on a sleeping instance

    The container slept after inactivity, so the first request each morning took eleven to eighteen seconds.

  • Medium

    No logs that survived a restart

    Output went to stdout and disappeared with the container. When something failed there was nothing to read, so every incident investigation started from a verbal description of what the screen had looked like.

Production ready

The work we did

Delivered over 2 weeks, on branches, alongside a founder who kept shipping product the whole time.

  1. 01

    Migrated the SQLite database to managed Postgres, keeping the schema and adding the constraints SQLite had been quietly ignoring - three foreign keys and two uniqueness rules that had already allowed duplicate bookings.

  2. 02

    Ran the migration twice: a full dry run against a copy to measure the window, then the real cutover, with the old file kept read-only for a fortnight.

  3. 03

    Replaced the development server with a production WSGI server behind a reverse proxy, sized to four workers, with health checks and graceful restarts.

  4. 04

    Containerised the app so the same image runs on a laptop, in staging, and in production.

  5. 05

    Moved every credential into a managed secret store, rotated all of them, and rewrote the repository history to remove the committed values.

  6. 06

    Put both admin routes behind the session check and added tests asserting that an anonymous request to each returns 401.

  7. 07

    Deployed to an always-on instance in the region the warehouse is in, so the first request of the day is no longer the slow one.

  8. 08

    Set up nightly automated backups with a monthly restore drill, structured JSON logging with thirty-day retention, error tracking, and uptime alerting into the operations channel.

Measured

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.

  • Data-loss events

    3 in 6 weeks0 in 9 months

    Measured since the migration. The database no longer lives on a disposable disk.

  • Measured uptime

    96.8%99.95%

    Thirty-day windows before and after, from the same external monitor.

  • p95 response time

    1.9 s310 ms

    Most of the gain is concurrency - one worker to four - plus connection pooling.

  • First request after idle

    11–18 sUnder 400 ms

    The instance no longer sleeps, so nobody has to warm it up before a shift.

  • Backup restore

    No backupsNightly, drilled monthly

    The most recent drill restored the 1.4 GB database in three minutes.

  • Monthly infrastructure cost

    $25/mo$95/mo

    Managed Postgres, an always-on instance, and real backups cost more than a sleeping container.

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.

  • Replit
  • Python + Flask
  • Gunicorn
  • PostgreSQL (managed)
  • Docker
  • Caddy
  • Fly.io
  • Sentry
  • Uptime alerting
We had blamed two people for losing that data. Finding out it was the disk was a relief and also not a good day.
Operations manager, logisticsParaphrased from a composite engagement and attributed by role only. No individual or company is identified.

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.

Book a Free Call