For development, I needed a simple application to test mails – something like Mailpit or MailHog, but with support for SendGrid and other mail services. So I thought I’ll vibecode one in NextJS, then rewrite it to Hono/Vite and rewrite it again to Go/Vite. All iterations are published as Docker containers (maildotnull/maildotnull) and I ran multiple tests on my notebook to check the resource consumption. The results were quite interesting, so let’s have a look:
The setup
The application itself is always the same: a small mail-testing tool that receives mails and shows them in a web UI. I measured the following for each iteration:
- Container size: the size of the Docker image
- Build time: the time GitHub Actions needs to build and publish the image
- Startup time: the time until the container is ready to serve requests
- Memory used: the memory consumption of the running container
NextJS (maildotnull/maildotnull:0.1.2)
The first version was written in NextJS, because it’s probably the fastest way to get a full-stack application up and running:
- Container size: 195.70 MB
- Build time on GitHub: 4m 48s
- Startup time: 956ms
- Memory used: 86.92 MB
It works, but almost 200 MB and nearly 90 MB of memory for a small mail-testing tool felt like a lot.
Hono/Vite (maildotnull/maildotnull:0.1.4)
The second iteration uses Hono as backend and Vite for the frontend. It’s still JavaScript/TypeScript, so the rewrite was quite easy:
- Container size: 172.43 MB
- Build time on GitHub: 2m 59s
- Startup time: 522ms
- Memory used: 54.27 MB
Better – the startup time was cut almost in half and the memory usage went down by ~35%. But the container size stayed pretty much the same, because you still need to ship the Node.js runtime with it.
Go/Vite (maildotnull/maildotnull:0.1.9)
For the third iteration, I kept the Vite frontend and rewrote the backend in Go. Go compiles to a single binary, so the container doesn’t need a runtime at all:
- Container size: 13.56 MB
- Build time on GitHub: 1m 50s
- Startup time: 175ms
- Memory used: 5.29 MB
That’s a huge difference! The container is ~14x smaller than the NextJS version, starts 5x faster and uses only 5 MB of memory instead of 87 MB. I ran the tests multiple times (0.1.5, 0.1.8, 0.1.9) and the numbers were stable – memory was always between ~4.9 and 5.5 MB.
Note: This is not a completely fair comparison – NextJS gives you a lot of features (SSR, routing, image optimization, …) that a small tool like this simply doesn’t need. If you build a large application and your team knows React well, then NextJS is still a valid choice.
But in my opinion, for small self-hosted tools that should run 24/7 in the background, the Go/Vite combination is hard to beat: tiny image, instant startup and a memory footprint that you won’t even notice on your server. That’s why maildotnull will stay on Go/Vite – and honestly, with today’s coding agents, rewriting a small application in another stack is not a big deal anymore.
If you want to try it yourself, you can run it with the following command:
docker run -p 8080:8080 maildotnull/maildotnull:latest
or
podman run -p 8080:8080 docker.io/maildotnull/maildotnull:latest
Further Information
- maildotnull on Docker Hub: https://hub.docker.com/r/maildotnull/maildotnull
- maildotnull on GitHub: https://github.com/maildotnull/maildotnull


Comments are closed