What the Builder Expects in Your Repo 📦
When you run faable deploy, the CLI inspects your repository, detects the stack, builds a container image (on your machine or in CI), and pushes it to Faable. 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 or the --buildpack (-b) CLI flag:
faable deploy --buildpack docker🟢 Node.js Projects
A minimal repo the builder accepts:
my-app/
├── package.json ← triggers Node detection
├── server.js
└── node_modules/ ← must be installed before deploying (see below){
"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 | Used as the app name. Deploy fails with Missing name in package.json without it. |
engines.node | No | Resolved to a concrete Node release (e.g. 22.x → latest 22). If omitted, the builder uses the Node version running the deploy — your machine locally, or the CI runner. |
scripts.build | No | If present, runs locally (npm run build) before packaging, with your app’s environment variables available. |
scripts.start | No | The container runs npm run start by default. If absent and a static framework is detected, a static serve command is generated instead. |
The image is node:<version>-slim and copies your working directory as-is — it never runs npm install. Dependencies are installed on the host before packaging: if node_modules is missing, faable deploy installs it automatically (npm ci with a lockfile, npm install otherwise), so the workflow needs no separate install step.
Static Frontends
If package.json has no start script, the builder looks at your dependencies to detect a static framework and serves its build output automatically:
| Framework detected | Build output served |
|---|---|
| Astro | dist/ |
| Gatsby | public/ |
Create React App (react-scripts) | build/ |
| Vue CLI | dist/ |
| Angular | read from angular.json |
| Vite | dist/ |
Next.js is the exception: it always runs as a server (next start), with a persistent cache for .next/cache managed by the platform. And remember — defining a start script disables static serving entirely; the builder trusts your script.
🐍 Python Projects
Detected by requirements.txt, pyproject.toml, Pipfile, or cerebrium.toml. Unlike Node, dependencies install inside the image build (pip install -r requirements.txt, pip install ., pipenv install, or the pip table of cerebrium.toml respectively). With a requirements.txt, the manifest is copied into its own image layer before the install, so rebuilding after a source-only change reuses the cached dependency layer.
Python version, first match wins:
runtime.txt— e.g.python-3.12.1.python-version- The manifest’s own version (e.g.
python_versionincerebrium.toml) requires-pythoninpyproject.toml- Default:
3.11.3
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 added to the image 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 any dependencies (the deploy logs warn loudly). Framework detection still works by reading the entrypoint file itself, and uvicorn/gunicorn are added when the start command needs them. 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 buildpack runs docker build on it verbatim (targeting linux/amd64, so images built on Apple Silicon run on the platform) — 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). PORT and FAABLE_HOST are reserved — secrets you define with those names are ignored.
⚙️ 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",
"buildCommand": "npm run build:prod",
"startCommand": "node dist/main.js"
}| 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. The --buildpack CLI flag beats it. |
buildCommand | Build step to run when package.json has no build script (Node), or custom install command (Python). |
startCommand | Overrides everything — framework detection and npm run start. |
❓ FAQ
My deployed app crashes with “module not found” — why?
The Node image copies your working directory as-is and never runs npm install. faable deploy installs dependencies on the host automatically when node_modules is missing, so this usually means the install was skipped or partial — check the deploy logs for the install step, and make sure the missing module is listed in dependencies (not only devDependencies if your start command needs it at runtime).
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 (or "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…) — 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 a concrete release. Without it, the builder uses the Node version of the machine running the deploy — pin engines.node so local and CI builds agree.
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?
No — faable deploy builds the image where it runs: your machine (Docker must be running) or your CI runner. Faable receives the finished image and runs it. 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.
- Express Guide — a complete Node.js example.
Last updated on