Railpack vs Dockerfile — choosing how your repo becomes a container

Published 2026-05-08 by tavin

Two paths can take your Git repo to a running container:

  1. An auto-builder reads the repo, infers the runtime, and produces the image.
  2. You write a Dockerfile and the platform builds that.

Railpack is the auto-builder tavin.cloud uses by default. It is the spiritual successor to Nixpacks and a contemporary of Cloud Native Buildpacks. All three solve the same problem: most apps do not actually need a hand-written Dockerfile, and the time it takes to write one for a small service is mostly wasted.

This post is about how to pick between the two paths and how a good platform lets them coexist.

What an auto-builder actually does

A modern auto-builder is, roughly:

  1. Inspect the repo for language and framework signals — package.json for Node, pyproject.toml for Python, go.mod for Go, Gemfile for Ruby, Cargo.toml for Rust, etc.
  2. Pick a base image (Alpine, Debian slim, distroless, depending on the builder).
  3. Install the runtime at the version the repo requests.
  4. Install dependencies via the lockfile-appropriate package manager.
  5. Run a build step if the repo declares one (bun run build, npm run build, pip install, go build, …).
  6. Set the start command, the listening port, and a sensible entrypoint.
  7. Output a container image.

Done well, this is invisible. You push the repo, you get a container. Done badly, you get the kind of mystery debugging where the build fails on the platform but works on your laptop and you cannot tell why.

Railpack’s bet is that an open-source, scrutable auto-builder is the right shape: the build plan is inspectable, the rules live in a public repo, and you can reproduce builds outside the platform.

When auto-detection is the right choice

Auto-detection is the right default when:

This is most internal services, most public APIs, and most early-stage products. The auto-detector is faster to set up and faster to maintain. There is nothing for a future engineer to have to read.

When a Dockerfile becomes load-bearing

A Dockerfile is the right choice when at least one of these is true:

In these cases, a Dockerfile is not a workaround for a missing auto-builder feature — it is the right artifact. It writes the runtime down. It is reviewable in pull requests. It works on every platform that can build a Dockerfile.

The escape-hatch principle

The point isn’t “auto vs hand-written.” It is “auto by default, explicit when needed, on the same platform.”

A well-designed PaaS treats this as a hierarchy:

  1. No Dockerfile in the repo → auto-build with Railpack.
  2. Dockerfile exists at the repo root → use it.
  3. A pre-built image is referenced explicitly → skip the build, deploy the image.

The repo is the cloud contract, and the contract knows which path to take. You do not have to commit to “we are an auto-detected app” or “we are a Dockerfile app” forever — you can start with auto-detection, add a Dockerfile the moment you need a system package, and the platform picks up the change without further configuration.

This is also why Buildpacks struggled in places where teams needed escape hatches but did not want to abandon the buildpack ecosystem entirely. The escape hatch needs to be obvious, supported, and not punishing to use.

What “good” looks like in 2026

A useful PaaS in 2026:

That last point is increasingly relevant. AI agents — Claude Code, Cursor, Codex — are now common participants in the workflow. An agent asked to “fix the build” needs to understand what the platform did, which step failed, and what the next change should be. A well-structured build plan is a much better surface than a colorized terminal log.

This is also why we treat MCP as the wire format for deployment rather than a CLI scrape: the agent sees the build plan as structured data.

A practical recommendation

Default to auto-detection. Add a Dockerfile the day you need one. Pick a platform that supports both paths cleanly.

Concretely, on tavin.cloud:

There is no “Dockerfile mode” or “auto mode” lock-in. The repo decides, the platform respects it, and the artifact at the end of every path is the same: a normal Linux container running on Kubernetes that you do not have to operate.

If you want to compare how this same dual-path contract works elsewhere, our side-by-side comparisons cover Render, Railway, Fly.io, and Vercel. They are written from our perspective — read with that bias in mind.

The right answer for any single repo is usually “start auto, switch to Dockerfile when you have a reason.” The right answer for the platform is “support both, equally well, on the same primitives.”