The short answer
Nixpacks does not weigh indicators by "importance" of your app — it scans the repository for known marker files (package.json, requirements.txt, go.mod, Cargo.toml, pom.xml, *.csproj, mix.exs, and similar) and picks the provider whose marker matches first in its internal provider ordering. When that scan finds nothing it recognizes, or finds conflicting signals it cannot resolve, the analysis phase aborts and the build fails before any image is produced. A manual Dockerfile is required only when detection is genuinely ambiguous or when your build needs steps the generated plan cannot express — not as a first response to any failure.
How the prioritization actually behaves
The confirmed facts are limited: Nixpacks runs a set of language providers, each of which inspects the repo for its indicator files, and exactly one provider wins. What is not guaranteed is a stable, documented ranking of providers against each other. The ordering is an implementation detail that has shifted between Nixpacks releases, so treat any specific "Node beats Python beats Go" list as a snapshot, not a contract. In practice, the common failure modes on Railway are:
- Multiple markers at the repo root — e.g., a package.json for a frontend plus a requirements.txt for a scripts folder. One provider wins and the other language is silently ignored, producing a broken runtime rather than an explicit error.
- Marker in a subdirectory — detection runs from the root; a monorepo with code under
server/ or apps/api/ may detect nothing or the wrong thing. - No marker at all — shell-script-driven or pre-built-artifact repos have nothing to match, so analysis fails outright.
Verify before you override
Run the detection locally to see what Railway sees:
nixpacks build . --verbose
The log lists which files were inspected and which provider produced the plan. If the chosen provider is wrong, the cheapest fix is usually not a Dockerfile — it is removing the ambiguity: move the stray marker file, set the Railway root directory to the correct subfolder, or pin the provider explicitly with a nixpacks.toml or the NIXPACKS_ environment variables (for example, forcing a Node version). You can confirm the effect by renaming the suspected indicator and re-running the build.
When a Dockerfile is genuinely required
- Mixed-language builds where both runtimes must coexist in one image (e.g., a Python app that shells out to a compiled Go binary) — a single provider plan cannot express this.
- Custom system dependencies or build steps beyond what Nixpacks setup/install/build phases allow, such as compiling native extensions against specific system libraries.
- Pinning a base image or runtime that Nixpacks does not offer, or reproducing an exact environment for compliance reasons.
- Detection fails with no marker you can add — e.g., deploying a prebuilt binary or static output.
The trade-off: a Dockerfile bypasses Nixpacks' layer caching and minimal base images, and you own keeping it in sync with the app. One caveat worth flagging — the exact provider priority order and tie-breaking behavior are internal and version-dependent, so if your build silently changed runtimes after a Railway/Nixpacks upgrade, that is the first thing to check with the verbose log above.