Every modern PaaS asks the same question at deploy time: how do you want your source code turned into a container? The three common answers are: write a Dockerfile, use Buildpacks, or use Nixpacks. They're all valid; they're best in different cases.
TL;DR
- Standard web app, common stack, no custom build steps? Use Nixpacks (or Buildpacks). It auto-detects Node / Python / Ruby / Go / PHP / Java and produces a sensible image. Zero config.
- Specific OS dependencies, FFI, or custom build pipeline? Write a Dockerfile.
- Multiple services in one repo / monorepo / one-of-a-kind setup? Dockerfile per service, or Nixpacks with explicit overrides.
What each one is
Dockerfile
You write the recipe. The PaaS runs docker build. You have full control over base image, OS packages, build steps, runtime user, environment, and so on. The downside is you have to maintain it — security patches, base image upgrades, layer caching strategy.
Buildpacks (CNCF Cloud Native Buildpacks)
Buildpacks are reusable, language-aware build recipes. They detect your stack (Node? Python? Java?) and produce a container image without you writing anything. Heroku invented the concept; it's now a CNCF standard.
Pros: zero config, security patches inherited automatically. Cons: harder to customise, slower than Dockerfile in many cases.
Nixpacks
A newer take by the Railway team, also adopted by Launchverse. Like Buildpacks but much faster, with cleaner overrides via a nixpacks.toml file. In practice, Nixpacks "wins" for most modern web stacks — it's faster, it's smaller, and it handles edge cases better than Buildpacks does.
When Dockerfile is right
Reach for a Dockerfile if:
- You need OS packages that aren't in the default Buildpack/Nixpacks set (
libsodium,wkhtmltopdf, etc.). - You're running native code that requires a specific compiler version.
- You have a complex multi-stage build (e.g. compile in a heavy image, copy artefacts to a slim runtime).
- You want a minimal final image (e.g. distroless) for security scanning.
- You have something unusual: ML model bundling, custom Java VM tuning, etc.
A Dockerfile is also the right move when you want builds to be portable across PaaS providers — every PaaS supports docker build; only some support Buildpacks or Nixpacks.
When auto-detection is right
Use Nixpacks (or Buildpacks) if:
- You're shipping a standard web app in a popular language.
- You want the platform to keep your base image patched without you noticing.
- You don't have time / inclination to maintain a Dockerfile.
- Your build is "install dependencies, build, start" with no exotic steps.
This describes 80% of new web apps in 2026.
Performance
Build speed, in our (informal) benchmarks on Launchverse:
| Stack | Dockerfile (cold) | Nixpacks (cold) | Buildpacks (cold) |
|---|---|---|---|
| Node 20 + Next.js | 2m 40s | 2m 10s | 3m 50s |
| Python 3.12 + FastAPI | 1m 20s | 1m 30s | 2m 10s |
| Go 1.22 (small) | 50s | 1m 0s | 1m 30s |
Cached rebuilds tilt heavily toward Dockerfile (where layer caching is best understood) but for cold builds Nixpacks is competitive and often wins on JS-heavy apps.
When monorepos enter
A repo with a frontend, an API, and a worker is best handled with three separate "applications" inside a Launchverse project, each with its own build target. You can:
- Use Dockerfile per app, with build context pointing at the relevant subdirectory.
- Use Nixpacks per app with
nixpacks.tomlfiles specifying the build path.
Avoid building one giant image that ships all three; deploy times balloon and a small UI tweak waits behind a slow worker rebuild.
Security tips
- Pin base image versions.
FROM node:20.11.1-alpinenotFROM node:latest. Your Dockerfile should produce the same image today as it does next month. - Run as non-root. Add
USER node(or a dedicated user) in production images. The default Nixpacks image already does this. - Multi-stage builds. Compile in one stage, run in a slim final stage. Reduces attack surface and pulls < 100 MB on the wire.
- Scan. Trivy or Grype, run in CI. Most issues are out-of-date base images.