There is a version of web development that looks like this: git pull, restart the process, done, no build step, no npm install on the server, no pipeline with six stages and a Slack notification when it finishes, no node_modules folder that takes longer to copy than the actual code, just the application, running. This is not a fantasy but precisely what Reepolee is built toward, and this post is about why that matters and for whom.
Not Every Application Is a SaaS Product
The web development conversation is dominated by the requirements of a specific kind of software: high-traffic consumer products, SaaS platforms, anything that needs to scale horizontally and deploy continuously and survive a mention on a popular website. The tooling, the conventions, the best practices - most of it evolved to serve that context.
But a very large number of applications don't live in that context - the booking system for a community center, the member directory for a trade union, the volunteer schedule for a food bank, the incident register for a facilities team. These applications have tens or hundreds of users, not millions. They run on a single VPS that costs a few euros a month. They are maintained by one developer, possibly part-time, possibly a volunteer.
For these applications, a bundler config is not a feature, a Docker container is not a requirement, and a deployment pipeline is not a safety net but overhead that one person has to understand, maintain, and debug when it breaks at the wrong moment.
The appropriate amount of complexity for a small application is as little as possible. Not because simplicity is aesthetically pleasing, but because every layer of complexity you add is a layer that can fail, a layer that needs updating, a layer between you and the running code when something goes wrong.
Bun Is Becoming an Application Server
Something has been happening with Bun that deserves naming directly: it is increasingly difficult to describe it as just a JavaScript runtime. Look at what it ships natively - an HTTP server with routing, a SQL client that supports MySQL, SQLite, and PostgreSQL, file I/O, streams, workers, WebSockets, S3 and blob storage APIs, password hashing, a test runner, a package manager, and direct TypeScript execution without a compilation step.
That is not a runtime in the traditional sense but most of what a web application needs to function, packaged into a single binary.
The things that used to require Express, a database driver, a session library, a file upload handler, a WebSocket library, a streaming response package - Bun simply has them. When a feature needs real-time updates, WebSockets are available natively - no library, no adapter, no compatibility shim. When a response needs to stream - a large file, a server-sent event feed, a live log - the streaming APIs are there, part of the same runtime, working the same way as everything else. Not as add-ons or packages you install, but as part of the runtime you already have. For a small to mid-sized application serving a bounded audience, the native Bun APIs cover the majority of the surface without reaching for a single external package.
This is why zero runtime dependencies is achievable now in a way it wasn't three years ago, because Bun did the work of consolidating what previously required a dozen packages into a single, coherent runtime.
One more thing worth saying about Bun, because the framing matters: stop thinking about it as a Node.js alternative. That framing - faster Node, drop-in Node replacement, Node but better - undersells what it actually is and anchors the conversation to a comparison that becomes less relevant with every release. Node compatibility is a welcome bonus that eases migration. It is not the point. The point is a runtime that was designed from the start with a coherent, wide native API, a fast execution model, and a clear opinion about what a JavaScript application server should be able to do without reaching for packages. That is a different thing from Node, not a better version of it.
Which means the shift worth making is not "run my Node code on Bun." It is "develop for Bun." Use Bun.serve() instead of Express. Use new SQL() instead of a database driver package. Use Bun.file() instead of fs. Write code that assumes Bun's native APIs are available, because they are, and because doing so is what lets you drop the dependencies, skip the build step, and deploy with a git pull. Treating Bun as a faster Node runner and continuing to reach for the same packages is leaving most of the value on the table.
The pattern isn't new. Go emerged from frustration - developers who were tired of slow compile times, fragmented tooling, and the ceremony of C++ and Java for server work. The Go team made a deliberate decision to build a wide standard library, to ship formatting and testing and building as part of the language itself, because they had watched the ecosystem solve those problems badly enough times to know that the ecosystem shouldn't be solving them at all. The result was a runtime with an opinion about what belongs inside it - and that opinion turned out to be correct.
Bun is not a new language. But the frustration it responds to is the same: slow startup, npm install as a ritual, needing a bundler and a test runner and a package manager and a TypeScript compiler as four separate tools to do what should be one thing. The response is also the same: one binary, wide native API, fast, with a clear view of what an application runtime should handle so that the application itself doesn't have to. Same philosophy, different decade, different language. The skepticism it faces sounds familiar too.
I've been through this before. When Node.js arrived, the reaction from large parts of the industry was dismissive to the point of hostility. JavaScript belongs in the browser. Use a real language for the server. It will never scale. It has no future. The people who ignored that and built with Node anyway were treated like they were making an obviously wrong choice - until the rest of the industry caught up and the "obviously wrong choice" became the default. Bun is the same evolution, one step further. The skepticism sounds familiar because it is the same skepticism, recycled.
Early adopters know what it feels like to be right before the consensus agrees.
There is a counterargument worth addressing directly, because it comes up whenever Bun's built-in APIs are discussed: this is wrong, the argument goes, APIs should be packages. The runtime should be minimal and composable. Let the ecosystem decide what belongs.
I disagree, and I think the previous section on supply chain attacks explains why better than any philosophical argument could. Every API that ships as a package instead of as part of the runtime is a dependency with a maintainer who might disappear, a version that might introduce a breaking change, a release that might contain malicious code, and an npm install that pulls it and everything it needs into your project. Multiply that by the dozen APIs a typical web application needs and you have the situation JavaScript has been in for years: projects with hundreds of transitive dependencies, most of which nobody on the team has ever read.
A single, well-maintained runtime with a wide native API is strictly better than the equivalent collection of packages, for every property that matters in production. It has one release cycle, one security team, one changelog to read. When Bun ships a SQL client, that client is tested against the same runtime your code runs on, maintained by the same people, versioned together. When you install a third-party SQL package, you have introduced a dependency whose test coverage, maintenance posture, and compatibility guarantees are somebody else's problem until the day they become yours.
The "runtime should be minimal" philosophy made sense when the ecosystem was young and the right abstractions weren't obvious. We know now what a web application needs. A runtime that ships those things natively isn't bloated - it's mature. A slightly larger binary in exchange for eliminating an entire class of dependency risk is not a trade-off. It's the better trade.
What Zero Runtime Dependencies Actually Means
Reepolee's package.json has two dev dependencies: TypeScript types for Bun, and the Tailwind CSS CLI, and that is the entire list, because the running application on the server has no npm packages at all.
The developer tools - the CSS compiler, the formatter, the linter, the code generator - are installed globally on the developer's machine. They live there, they work there, and they never appear on the VPS. The server doesn't know they exist. This is the relationship that works well between development tooling and production infrastructure: the tools that help you build should not be part of what you deploy.
Third-party JavaScript libraries that do appear in the application - a validation library, a syntax highlighter - are vendored: downloaded once as minified ESM files, committed to the repository, served directly by the application. No npm install pulls them. No CDN serves them. They are just files in the repo, stable, auditable, and present whether or not the registry is up.
The result is a server that needs exactly two things: Bun and the repository, which means git pull and a process restart, with no install step that could fail because a package has been unpublished, no build step that requires the right Node version, and no node_modules directory that has to be synchronized or cached or reproduced.
The .ree Templates and Server-Side Rendering
The application generates HTML on the server, not through a client-side framework that downloads JavaScript and hydrates and then renders, but by having the server read a request, run the template, and send a complete page that the browser receives and displays without any further ceremony.
The .ree template format compiles on the server at request time, with no bundle to generate and no client-side JavaScript framework to initialize, because the template is the view, the server is the engine, and the output is what the browser needs to display the page - nothing more. Client-side JavaScript lives in the template file when it's needed for a specific interaction, served as-is, without a bundler in the chain.
For the kind of application this post is about - a sports club's registration portal, a community clinic's appointment diary, a small library's lending tracker - this is not a constraint but the right fit, because the entire model fits in one sentence: the user navigates to a page, the server sends it, and the browser shows it.
Complexity Has a Cost You're Not Always Shown
The case for build pipelines and dependency ecosystems is usually made in terms of capability: look at what you can do with this. The case against is made in terms of cost, and that case is less often made explicitly.
Every dependency is a thing that can have a vulnerability, be abandoned by its maintainer, change its API without warning, or simply stop working when another package in the tree updates.
The supply chain attack deserves more than a mention in passing, because it is one of the most underappreciated risks in modern JavaScript development and it scales directly with your dependency count. When you run npm install, you are not just installing the packages you listed. You are installing everything those packages depend on, and everything those dependencies depend on, recursively. A medium-sized JavaScript project can easily have hundreds of transitive dependencies - code written by people you have never heard of, reviewed by nobody on your team, running in your production environment with access to your file system, your environment variables, and your network.
This has gone wrong in visible, public ways. A widely used utility package was compromised and published malicious code that spread to thousands of projects before anyone noticed. A developer intentionally broke their own package used by millions, in protest. Another injected code that targeted specific IP ranges. These are not edge cases or theoretical scenarios - they are things that happened, documented in public post-mortems, and the common thread in each of them is that the attack surface was the dependency tree itself.
Vendoring changes this relationship entirely. When a third-party library is a file in your repository - downloaded once, inspected, committed - it cannot be silently updated by someone else. It does not change between your last deploy and tonight's. It does not disappear because the author deleted their account. You know exactly what it is, because you can read it. The smaller your dependency list, the smaller your attack surface, and the more you know about what is actually running on your server.
Every build step is a thing that can fail. The CSS compilation, the bundling, the transpilation, the minification - each of these is a process with configuration, with version requirements, with environmental assumptions. Each one has failed for someone at the worst possible moment. Removing build steps from the production path removes that category of failure entirely.
None of this means build tools are wrong, but it does mean that every tool you add should earn its place by providing value that exceeds its cost, and for a small application with a small team and a bounded audience most of them don't.
Who This Is For
A system that deploys with a git pull and restarts with one command is not a toy. It is a well-suited tool for a specific and very common context: applications where the audience is known, the scale is bounded, the team is small, and the most important thing is that the system works correctly and can be understood and maintained by the people responsible for it.
Credit unions, volunteer organizations, youth sports leagues, professional associations, municipal offices, internal ops teams - any organization that needs real software but doesn't have an engineering team to run a deployment infrastructure.
For these contexts, the honest comparison is not "this versus a modern SaaS deployment" but "this versus the spreadsheet it's replacing, or the off-the-shelf system that costs ten times as much and does half of what's needed," and against that comparison a zero-dependency, no-build application that runs on a five-euro VPS and deploys in seconds is not a compromise.
Reepolee is built for exactly this kind of context, where the generator produces the application, Bun runs it, the server hosts it, and nothing else is required, because that absence is the feature.
The natural next question is where that server lives - and the answer matters more than most developers give it credit for. That's a conversation about on-premise deployments and owning your own VPS, which deserves its own post.