BACK TO ENGINEERING
Runtime 8 min read

I Deleted a 200MB Rust Binary From My Docker Image. My Deploys Have Never Been the Same.

Article Hero

There is a 200-megabyte elephant hiding in every Prisma project's Docker image.

It is a Rust binary. It is larger than most entire applications. And for five years, it has been the cost of admission for using the most popular ORM in the JavaScript ecosystem.

With Prisma 7, I killed it.

This is what happened when I migrated mentoring.oakoliver.com from Prisma 5 to Prisma 7's pure-JavaScript driver adapter, running on Bun against PostgreSQL 17. The Docker image dropped from 512MB to 298MB. Cold starts fell from 2.1 seconds to 680 milliseconds. And the deployment pipeline finally stopped feeling like it was hauling dead weight.

But it was not a clean upgrade. There were traps everywhere.

Let me walk you through every single one.


I – The Ghost in Your node_modules

Before we celebrate the Rust engine's death, you need to understand why it existed in the first place.

Prisma was always architecturally unusual. Most ORMs — Knex, Drizzle, TypeORM — generate SQL strings in JavaScript and hand them to the database driver. Simple. Direct.

Prisma took a different path. It introduced a query engine written in Rust that sat between your application and the database. Your JavaScript called a method. That call got serialized into an internal protocol. The protocol was sent to the Rust engine via IPC. The Rust engine compiled it into SQL, executed it, deserialized the results, and sent them back.

Why all this complexity? Query optimization. Connection pooling. Cross-database abstraction. Type enforcement at the query level.

In theory, elegant.

In practice, every single project using Prisma also shipped a platform-specific binary that was four times the size of the actual application code.

If you were building Docker images for linux/amd64 on an arm64 Mac, you needed both binaries during the build phase. Your CI pipeline was downloading hundreds of megabytes of Rust before it even looked at your JavaScript.

For the mentoring platform — a React 19 SSG frontend with an Elysia.js API — the total application code is around 45MB. The Prisma engine alone was over 200MB.

The binary was the application. Everything else was a footnote.


II – The Architectural Shift Nobody Saw Coming

Prisma 7 introduced a fundamentally different approach called driver adapters.

Instead of routing every query through a Rust engine, Prisma now generates SQL directly in JavaScript and executes it through a standard database driver that you provide.

The key insight is a separation of concerns that should have existed from the start. Query planning — schema introspection, relation resolution, type mapping — was rewritten in JavaScript. Query execution — connecting to the database, running SQL, returning results — is now delegated to whatever driver you already use.

One less process. One less binary. One less point of failure.

For PostgreSQL, this means the Prisma client talks directly to the pg driver. No Rust intermediary. No IPC hop. No mysterious binary lurking in your node_modules.

The Rust engine is completely eliminated from the runtime.


III – The Migration That Bit Back

Let me walk through exactly what happened when I migrated the mentoring platform. This is the real sequence, including the mistakes.

The first step was upgrading dependencies. You still need the Prisma CLI as a dev dependency and the client as a production dependency. The difference is what happens during generation. In Prisma 5, generation downloaded the Rust binary. In Prisma 7 with a driver adapter configured, generation produces only JavaScript — type-safe client code, query builders, schema metadata. No binary download.

The second step was updating the schema. A single line in the generator configuration tells Prisma to produce a client that expects a driver adapter instead of looking for a Rust engine. That one line changes everything downstream.

The third step was where the real architectural shift lives. Instead of just instantiating the client, you now construct a standard connection pool, wrap it in the adapter, and pass it to the client.

This is the most important change. You now own the connection pool. In Prisma 5, the Rust engine managed its own internal pool, and you could only configure it through the connection string. Now you have a standard pool instance that you can monitor, tune, and share across your application.

The fourth step was running generation and watching the magic. The output was different from anything I had seen before. No binary. The generated client directory went from 218MB to 3.4MB.

The fifth step was updating the Dockerfile. The runner stage no longer needed to copy a massive Rust binary between build stages. Selective copying of only the JavaScript client, the adapter package, and the pg driver was enough.


IV – Four Traps That Will Break Your Migration

Here is where things got painful.

Trap one: Decimal fields return strings. With the Rust engine, Decimal fields were silently converted to JavaScript numbers — lossy, but invisible. With the driver adapter, Decimal fields come back as strings. Every calculation in the codebase that did math on a Decimal field broke instantly.

This is actually a correctness improvement. The Rust engine was silently converting exact decimal values into IEEE 754 floating point. For financial calculations — and the mentoring platform does real-time session billing — this is wrong. The string representation forces you to use proper decimal arithmetic.

But it broke things. Every single place in the codebase that touched a Decimal column needed updating.

Trap two: raw queries behave differently. The return types from raw queries shifted because the pg driver's type mapping differs from the Rust engine's. BigInt columns now return actual BigInt values instead of numbers. Correct, but it breaks JSON serialization.

Trap three: connection pool errors changed shape. Pool exhaustion errors now come from the pg driver, not the Rust engine. Different error messages, different stack traces, different monitoring alerts. If you have Sentry or any error tracking set up, your alert patterns need updating.

The upside? You can now directly access pool statistics — total connections, idle connections, waiting requests. This was impossible with Prisma 5. The Rust engine's internal pool was a black box.

Trap four: interactive transactions need extra configuration. Transaction timeout and max wait settings moved to a different configuration surface. And the transaction isolation level defaults, while technically the same for PostgreSQL, need to be double-checked if you were relying on connection string settings.


V – The Numbers That Made It Worth Every Headache

Here are the actual measurements from the mentoring platform deployment on our Hetzner VPS managed by Coolify.

Docker image size dropped 42%. From 512MB to 298MB. The generated Prisma client directory went from 218MB to 3.4MB — a 98% reduction. Cold build times fell 56%. Cached builds fell 52%.

Cold start to first query dropped 68%. From 2.1 seconds to 680 milliseconds. This is the headline number. The platform recovers from deploys and container restarts dramatically faster. Health checks pass sooner. Old containers retire faster.

Query performance improvements were modest — 6 to 10 percent for typical queries. This makes sense. The actual SQL execution time dominates, and the database does not care whether the SQL came from a Rust engine or a JavaScript query builder.

Batch inserts improved by 30%. The Rust engine had overhead serializing large result sets across the IPC boundary. With the driver adapter, results flow directly from the pg driver into the Prisma client's JavaScript deserializer.

Memory usage dropped 37% at idle. The Resident Set Size fell from 142MB to 89MB because the Rust engine process is gone. Heap usage actually increased slightly — the JavaScript query builder does work that was previously done in Rust. But the net savings come entirely from eliminating the extra process.


VI – Bun-Specific Gotchas

Running Prisma 7 on Bun instead of Node.js introduces platform-specific behavior worth knowing about.

Bun has its own PostgreSQL client, but as of early 2026, the Prisma adapter expects the pg npm package. The pg package uses Node.js's net module for TCP, which Bun polyfills. In benchmarks, the overhead of this polyfill layer is negligible — under half a millisecond per query.

One gotcha during generation: the Bun and Node.js runners can behave differently. Using the Bun-specific flag during generation ensures Bun's runtime handles schema resolution and client writing correctly.

And an important distinction: the query engine is gone from production, but the migration engine still exists in development. Prisma's schema diffing and migration SQL generation still use a Rust binary. It is only needed during development and CI — never in your production Docker image.


VII – When You Should NOT Migrate

Not every project should rush to Prisma 7 driver adapters.

If you use Prisma with both PostgreSQL and MySQL, driver adapters mean a separate adapter for each database. The Rust engine handled this transparently.

If your codebase relies extensively on raw queries with specific type expectations from the Rust engine, the migration cost is high.

If your Docker images build in CI where size does not matter, and your cold starts are fine, the migration risk may not justify the benefit.

But if you are deploying to a VPS, a container orchestrator, or anywhere that cold starts and image size matter — this migration is the single biggest improvement you can make to your deployment story.


VIII – The Bigger Picture: ORMs Are Becoming Thin Layers

Prisma 7's driver adapter is not just a performance optimization. It is a philosophical shift.

The trend in the JavaScript ORM space is toward thinner abstractions over standard database drivers. Drizzle has always worked this way. Kysely does the same. Now Prisma, the most popular ORM in the ecosystem, is following suit.

Fewer surprises. Better debugging. Smaller deployments. More control.

For the mentoring platform, this migration was the single biggest improvement to our deployment story this year. A 42% smaller Docker image means faster deploys through Coolify, faster rollbacks when something goes wrong, and less storage consumed on our Hetzner VPS.


If you are building on Bun, Elysia, and PostgreSQL — or if you are wrestling with bloated Docker images and slow cold starts — I have been down this road. I offer one-on-one technical mentoring sessions at mentoring.oakoliver.com where we can dig into your specific stack and migration strategy. I have also built vibe.oakoliver.com for teams that need lightweight, production-ready tooling without the bloat.


IX – The Rust Engine Is Dead. Long Live Pure JavaScript.

The Rust engine served its purpose. It bootstrapped Prisma's ecosystem and proved that a type-safe ORM could work in JavaScript.

But the JavaScript runtime landscape has matured. Bun, Deno, and modern Node.js are fast enough that the Rust intermediary is no longer necessary.

The migration took me about six hours for the mentoring platform, including fixing all the Decimal type issues and updating the Docker build. For a smaller project, you could do it in an afternoon.

Start planning your migration now. The driver adapter approach is not just faster and smaller — it is fundamentally more debuggable and maintainable.

The question is not whether to migrate. The question is how much longer you are willing to ship a 200MB binary that does nothing your JavaScript runtime cannot do on its own.

What is the heaviest dependency lurking in your Docker image — and what would it mean to eliminate it?

– Antonio

"Simplicity is the ultimate sophistication."