Engineering

From Jupyter notebook to production: what actually has to change

Somewhere on your machine is a notebook — or a folder of Python scripts — that does something genuinely valuable. It cleans the data, runs the model, produces the numbers. It got you the paper, the grant milestone, or the demo that convinced someone to pilot it. And now somebody has asked the question that changes everything: "can we run this ourselves?"

The honest answer is usually no — not because the work is bad, but because research code and production code are optimized for different things. Research code is optimized to produce a result once, correctly, with you watching. Production code is optimized to produce results repeatedly, unattended, while everything around it misbehaves.

Here is what actually has to change — and, just as importantly, what doesn't.

What can stay

Start here, because most advice on "productionizing" research code quietly implies you should rewrite everything. You shouldn't. The science — the transformation logic, the model, the reasoning rules, the queries — is the validated part. It has survived peer review, evaluation runs, or months of your own scrutiny. That validation is worth more than any code style, and a rewrite throws it away for cosmetics. Production-hardening keeps the science and rebuilds the scaffolding around it.

The run-by-hand problem

A notebook runs top to bottom because you run it top to bottom — and you know the ritual: which cell to re-run after changing the config, which script must go first, which environment variable has to be set. That knowledge lives in your head, and production means it can't.

What changes: the pipeline becomes something a machine can run — one entry point, explicit configuration, dependencies pinned so the install works on a machine that isn't yours. This is the least glamorous part and the single biggest difference between "our prototype" and "our software."

The happy-data problem

Your pipeline was written against your dataset — curated, cleaned, shaped the way the paper needed it. Production data arrives malformed, incomplete, and encoded in ways nobody chose on purpose. Research code hits the first bad record and stops with a stack trace; production code logs it, skips or quarantines it, and finishes the run.

What changes: error handling stops being an afterthought. Every input boundary — file parsing, API payloads, external services — gets a decision about what happens when the input is wrong. In research this never matters, because you fix the data and re-run. In production nobody is there to re-run.

The localhost problem

If your prototype has an API layer — FastAPI is the usual suspect — it was probably written to serve one very trusted user: you. Endpoints with no authentication are fine on localhost. Queries that scan everything are fine on the evaluation set. Returning the full internal error to the caller is helpful during development.

All three are liabilities the moment a pilot partner's network can reach the service. What changes: authentication on every endpoint, limits on what a single request can ask for, and errors that get logged internally instead of served to strangers. None of this is research; all of it is table stakes.

The proof problem

A paper is checked by peer review. A codebase is checked by tests — and research code almost never has them, because the deadline was the conference, not the customer. That's rational right up until someone else needs to trust the code: an industry partner running a pilot, an investor doing diligence on your spinout, an engineer joining the project.

What changes: the core paths get tests. Not everything — the goal is not coverage theater — but the transformations and calculations the results depend on, so that the next change doesn't silently break the science.

The "it works on my machine" problem

Docker helps, and most research prototypes have a Dockerfile by now. But a Dockerfile written to get the demo running is not a deployment: secrets end up baked into images or committed to the repo, there's no way to tell whether the service is up, and the difference between the dev setup and the "real" setup exists only in your memory.

What changes: configuration and secrets move out of the code, the system starts cleanly from a fresh machine with documented steps, and something — even something simple — tells you when it's down before your pilot partner does.

The order to do it in

If you do this yourself, do it in the order above — it's roughly the order of pain. Runability first, because everything else depends on other people being able to execute the code. Input handling second, because that's what fails on day one of a pilot. Security third, before the service leaves localhost. Tests and deployment last, because they harden what the first three stabilized.

And if you'd rather have the map before the journey: this is exactly what our code audit produces for research prototypes — a senior engineer reads the codebase and delivers a written report of what's solid, what's fragile, and the prioritized path to deployable. Fixed price, $796 for academic teams, five business days, and the report is yours to execute with anyone.

Related: is your app production-ready? the checklist · research prototype to production — the full offer

Request an academic code audit →

Talk to us about your project

Senior engineers, honest scoping, fixed prices — quoted before work starts, paid when you approve the result.

Start a conversation