What the Builder Expects in Your Repo 📦
When you run faable deploy (or push to a linked repo), your project sources are uploaded and Faable’s remote builder inspects them, detects the stack and builds your app — no Docker on your machine, no CI build step. Detection is file-based — this page describes exactly which files the builder looks for and what it does with them, so you can shape your repo to deploy with zero configuration.
🔍 How Detection Works
Deploys are built by buildpacks (Heroku/CNB-style): each one checks for its trigger files at the project root, in order — the first match decides how your app is built:
| Priority | File found | Buildpack |
|---|---|---|
| 1 | package.json | Node.js (see below) |
| 2 | requirements.txt, pyproject.toml, Pipfile, or cerebrium.toml | Python (see below) |
| 3 | Dockerfile | Docker — your Dockerfile, built verbatim |
| 4 | main.py, app.py, or wsgi.py (fallback, no manifest at all) | Python without dependency install (see below) |
| — | None of the above | ❌ Deploy fails with a diagnostic listing what was looked for, what your repo contains, and any config from another platform it recognizes |
[!IMPORTANT] The order matters: if your repo has both a
package.jsonand aDockerfile, it is built as a Node.js project and the Dockerfile is ignored. The Dockerfile path is the escape hatch for stacks the buildpacks don’t detect natively.
You can skip detection entirely and force a buildpack — node, python, or docker — with the buildpack field in faable.json:
{ "buildpack": "docker" }A forced buildpack still runs its own detection, so the plan is computed from your real project files — forcing docker without a Dockerfile fails with a clear error.
🟢 Node.js Projects
A minimal repo the builder accepts:
my-app/
├── package.json ← triggers Node detection
└── server.js{
"name": "my-app",
"scripts": {
"build": "tsc",
"start": "node server.js"
},
"engines": {
"node": "22.x"
}
}What each piece means to the builder:
| Field | Required | What the builder does with it |
|---|---|---|
name | Yes | Deploy fails at detection with Missing name in package.json without it. |
engines.node | No | Resolved to a concrete Node release (e.g. 22.x → latest 22). Supported majors: 20, 22, 24. If omitted, the builder uses its own current Node version, which changes as the platform upgrades — pin it. |
scripts.build | No | If present, the builder runs npm run build before packaging, with your app’s environment variables available to the build. |
scripts.start | No | The app runs npm run start by default. If absent and a static framework is detected, a static serve command is generated instead. |
Dependencies are installed by the builder when no node_modules is present in the uploaded sources (for a monorepo, a node_modules at the workspace root counts). The lockfile picks the tool: package-lock.json/npm-shrinkwrap.json → npm ci, yarn.lock → yarn install --frozen-lockfile, pnpm-lock.yaml → pnpm install --frozen-lockfile, no lockfile → npm install. Dev dependencies are always included (build tools live there). If a frozen install fails — e.g. a stale lockfile — the builder retries with a plain npm install rather than failing the deploy.
Start command precedence: startCommand in faable.json → framework-detected static serve → npm run start.
Static Frontends
If package.json has no start script, the builder looks at your dependencies (including devDependencies, and hoisted monorepo dependencies) to detect a static framework and serves its build output automatically:
| Framework detected | Build output | Served with |
|---|---|---|
| Astro | dist/ | astro preview |
| Gatsby | public/ | gatsby serve |
Create React App (react-scripts) | build/ | static server, SPA fallback |
Vue CLI (@vue/cli-service) | dist/ | static server, SPA fallback |
| Angular | read from angular.json | static server, SPA fallback |
| Vite | dist/ | vite preview, SPA fallback |
For Angular, the output path comes from angular.json (architect.build.options.outputPath), with the browser/ subfolder handled automatically for Angular 17+ builders. SPA fallback means unknown routes rewrite to index.html — Astro and Gatsby build real per-page files, so they 404 instead.
Next.js is the exception: it always runs as a server (never static), with a persistent cache for .next/cache managed by the platform. When your start script is the stock next start, the builder ships Next’s compact standalone output automatically; a custom start command (or "next": { "standalone": false } in faable.json) opts out and ships the full workspace. And remember — defining a start script disables static serving entirely; the builder trusts your script.
Monorepos (Root Directory)
To deploy one app from a monorepo (Turborepo, npm/yarn/pnpm workspaces), declare its Root Directory with rootDir in a faable.json at the repository root:
{ "rootDir": "apps/api" }The builder then:
- installs dependencies at the workspace root (hoisted packages resolve normally),
- detects, builds and packages the app inside the Root Directory,
- for Next.js, points the standalone output tracing at the workspace root so shared packages are included.
The path is relative to the repository root — absolute paths and .. segments are rejected, and the build fails with a clear error if the directory doesn’t exist.
[!NOTE] For monorepos that deploy several Faable apps from the same repository, a single
rootDircan’t distinguish them — a platform-managed Root Directory is set per app instead (it takes precedence overfaable.json). Contact support to set it up.
🐍 Python Projects
Detected by requirements.txt, pyproject.toml, Pipfile, or cerebrium.toml (first match wins when several exist). The builder creates a virtual environment and installs dependencies into it according to the manifest:
| Manifest | Install |
|---|---|
requirements.txt | pip install -r requirements.txt |
pyproject.toml | pip install . |
Pipfile | pipenv install --system --deploy |
cerebrium.toml | the [cerebrium.dependencies.pip] table |
A buildCommand in faable.json replaces the manifest install entirely — use it when you need a custom install step.
Python version, first match wins:
runtime.txt— must be in the formpython-<version>(e.g.python-3.12.1).python-version- The manifest’s own version (e.g.
python_versionincerebrium.toml) requires-pythoninpyproject.toml- Default:
3.11
Supported Python minors: 3.11 and 3.12 — the app runs on Faable’s shared runtime image for the resolved minor.
Start command, first match wins:
startCommandinfaable.json- A
web:line in aProcfile - A start command declared by the manifest (e.g. the Cerebrium
entrypoint) - Framework auto-detection:
- Django (
manage.py+ a package withwsgi.py) →gunicorn <pkg>.wsgi:application --bind 0.0.0.0:$PORT - FastAPI / Starlette →
uvicorn <module>:app --host 0.0.0.0 --port $PORT - Flask →
gunicorn <module>:app --bind 0.0.0.0:$PORT
- Django (
For FastAPI and Flask the builder finds your app module by checking, in order: main.py, app.py, asgi.py, wsgi.py, application.py, server.py, app/main.py, app/app.py, src/main.py — preferring the file that actually defines app = FastAPI(...) / app = Flask(...). gunicorn/uvicorn are installed automatically if the start command needs them and they’re not in your dependencies.
If no framework is recognized and there’s no Procfile or startCommand, the deploy fails and asks you to provide one.
Cerebrium Projects
A repo shaped for Cerebrium — a cerebrium.toml plus a Python entrypoint, no requirements.txt — deploys out of the box. Faable reads the [cerebrium.dependencies.pip] table (and [cerebrium.dependencies.paths] pip when present) to install dependencies, python_version from [cerebrium.deployment], and the [cerebrium.runtime.custom] entrypoint as the start command when set. apt and conda tables are not installed. If your repo also has a classic manifest, the classic manifest wins.
Because Faable runs web services, your project still needs a web entrypoint (a FastAPI/Flask app, a Procfile, or startCommand in faable.json) — a bare GPU function without one fails with an explanation.
Python Without a Manifest
A lone main.py / app.py / wsgi.py with no dependency manifest at all is picked up by the Python fallback: the app builds without installing your dependencies (the deploy logs warn loudly; the web server itself — uvicorn/gunicorn — is still installed when the detected start command needs it). Framework detection still works by reading the entrypoint file itself. If your app imports anything beyond the standard library, add a requirements.txt. Note this fallback loses against a Dockerfile — an explicit Dockerfile always wins over a loose .py file.
🐳 Dockerfile Projects
No package.json, no Python manifests, but a Dockerfile? The builder runs it verbatim with BuildKit on Faable’s build infrastructure (targeting linux/amd64), pushes the image and pins the deploy to its digest — you control everything. Just honor the port contract below. If a package.json with a next dependency sits beside the Dockerfile (reachable by forcing "buildpack": "docker"), the deploy is registered as a Next.js app so the platform provisions its build cache.
🔌 The Port Contract
Whatever the stack, your app must listen on 0.0.0.0 at the port given by the $PORT environment variable (the platform sets it to 80):
app.listen(process.env.PORT, '0.0.0.0')The platform also injects FAABLE_HOST (your app’s public URL). Platform-managed names — PORT, FAABLE_HOST, FAABLE_APP_ID, FAABLE_DEPLOY_ID, FAABLE_RELEASE, FAABLE_GIT_COMMIT, FAABLE_GIT_REF — are reserved; secrets you define with those names are ignored. See Environment & Releases for the full list of injected variables and how the release version is resolved.
⚙️ faable.json Reference
faable deploy link creates this file at your project root to bind the repo to an app. All fields are optional:
{
"app_id": "app_xxx",
"app_slug": "my-app",
"buildpack": "docker",
"rootDir": "apps/web",
"buildCommand": "npm run build:prod",
"startCommand": "node dist/main.js",
"next": { "standalone": false }
}| Field | Purpose |
|---|---|
app_id / app_slug | Which Faable app this repo deploys to (written by faable deploy link). |
buildpack | Force a buildpack (node, python, docker) instead of auto-detection. |
rootDir | Monorepo Root Directory — the subdirectory the app builds from. The app’s Root Directory in the dashboard takes precedence when set. |
buildCommand | Node: build step used when package.json has no build script (the build script wins otherwise). Python: replaces the manifest install. |
startCommand | Overrides everything — framework detection and npm run start. On a Next.js app it also opts out of the standalone output. |
next.standalone | Set false to ship the full workspace instead of Next’s compact standalone output. |
❓ FAQ
My deployed app crashes with “module not found” — why?
Usually the module isn’t installed where the app runs: check that it’s listed in dependencies (not only devDependencies if your start command needs it at runtime), and check the deploy logs for the install step. In a monorepo, make sure the app’s Root Directory is set so workspace packages resolve.
I have a Dockerfile but Faable ignores it — why?
Because there’s also a package.json (or Python manifest) at the root, which takes precedence. Force it with "buildpack": "docker" in faable.json, remove the manifest from the root, or embrace the zero-config Node/Python buildpacks.
My repo was made for another platform (Cerebrium, Replicate, Fly…) — will it deploy?
Cerebrium projects deploy natively (see above). For other platforms, the detection error names the config file it recognized (cog.yaml, fly.toml, render.yaml, vercel.json, netlify.toml, railway.json, heroku.yml…) — Faable can’t consume those directly; add one of the supported manifests or a Dockerfile.
Which Node version does my app run on?
The version from engines.node in package.json, resolved to the latest release of that major (supported majors: 20, 22, 24). Without it, the builder uses its own current Node version, which changes as the platform upgrades — pin engines.node so your app’s runtime is deterministic.
How do I deploy a single app from a monorepo?
Set the app’s Root Directory in the dashboard (or rootDir in faable.json). Dependencies install at the workspace root, and the app builds from the subdirectory — hoisted packages and shared workspace libraries resolve normally. One Faable app per deployable package.
What port should my app listen on?
Read $PORT and bind 0.0.0.0. Hardcoding localhost or another port is the most common cause of an unresponsive app.
Does the build run on Faable’s servers?
Yes — faable deploy uploads your project sources and the build runs on Faable’s build infrastructure; no Docker is needed on your machine. faable deploy link can scaffold a GitHub Actions workflow that deploys on every push to main.
🔗 Related
- Get Started — link a repo and ship your first deploy.
- Runtime — how your app runs: restarts, env vars, app manager.
- GitHub Actions — deploy from CI on every push.
- Framework guides — complete end-to-end examples: Express (Node.js), Django, FastAPI, Flask.
Last updated on