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 | composer.json | PHP (see below) |
| 2 | package.json | Node.js (see below) |
| 3 | requirements.txt, pyproject.toml, Pipfile, or cerebrium.toml | Python (see below) |
| 4 | Dockerfile | Docker — your Dockerfile, built verbatim · Hobby or Pro (why) |
| 5 | index.php, or any .php file in the root or public/ (fallback) | PHP without dependency install (see below) |
| 5 | main.py, app.py, or wsgi.py (fallback, no manifest at all) | Python without dependency install (see below) |
| 6 | index.html in the root, public/, dist/, build/ or _site/ | Static — the files are the site, served directly by Faable with no container (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.PHP is the exception: a
Dockerfilebeside a PHP project wins, and the app keeps building from your own image. PHP repositories that already ship one are running on it — detection never re-platforms them. Force"buildpack": "php"infaable.jsonto take the managed runtime instead.
[!WARNING] The Docker buildpack requires a paid plan. Docker and Dockerfile builds — and deploying a prebuilt image — are available on Hobby and Pro. Free apps build with the managed Node, Python and PHP buildpacks; a free-plan deploy that resolves to Docker stops before the build with a plan message rather than a build error. See Docker and bring-your-own-image. Note the precedence above works in your favour here: a repo with both a
package.jsonand aDockerfileis a Node.js build, so it deploys on Free unchanged. The PHP exception cuts the other way, though — aDockerfilebeside a PHP project wins, so a repo of plain.phpfiles with aDockerfileresolves to Docker and needs theDockerfileremoved (or acomposer.jsonadded) to build on Free.
You can skip detection entirely and force a buildpack — php, node, python, docker, or static — 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. Forcing docker is subject to the same plan requirement as detecting it.
🟢 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 and 26. If omitted, a new app gets the newest LTS (24 today) and keeps that major from its first successful deploy on — it never changes under you. |
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. When a static framework is detected and there is no start — or it only serves the build output — the build is served as a static site instead, with no container. |
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 → static site (a detected static framework with no start, or a start that only serves its build output) → npm run start.
Static Frontends
The builder looks at your dependencies (including devDependencies, and hoisted monorepo dependencies) to detect a static framework, runs your build, and serves the build output directly — there is no container and no server process. The files are compressed (Brotli or gzip), fingerprinted assets are cached for a year, and there is no cold start: the first visitor gets the site as fast as the hundredth.
| Framework detected | Build output | SPA fallback |
|---|---|---|
| Vite | dist/ | Yes |
Create React App (react-scripts) | build/ | Yes |
Vue CLI (@vue/cli-service) | dist/ | Yes |
| Angular | read from angular.json | Yes |
| Astro (static output) | dist/ | No |
| Gatsby | public/ | No |
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 a page route the build doesn’t contain (/dashboard/settings) returns index.html, so client-side routers work on a hard refresh; a missing file (/assets/old.js) still returns 404. Astro and Gatsby build a real file per page, so an unknown route 404s instead — with your 404.html if the build has one.
[!TIP] You don’t need your own server for a single-page app. A small Express server with
express.static('dist')and a catch-all that returnsindex.htmlis exactly what Faable already does for you — writing it only turns your site into a Node.js process that has to start before it can answer. Delete thestartscript and let the builder servedist/.
A start script is taken at its word: if it runs your own code (node server.js, an Express or Fastify app), Faable runs it in a Node.js container, which is what a real backend needs. A start that only serves the build output — vite preview, serve -s dist, http-server dist — is recognized as a static server and the site is served statically anyway.
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.
Monorepos (Root Directory)
To deploy one app from a repository holding several, declare its Root Directory with rootDir in a faable.json at the repository root:
{ "rootDir": "apps/api" }This works for both shapes a monorepo comes in:
- A workspace (Turborepo, npm/yarn/pnpm workspaces) — a
package.jsonat the repository root ties the packages together. - Sibling folders —
client/andserver/, each self-contained with its ownpackage.jsonand lockfile, and nothing at the repository root.
The builder then:
- installs dependencies at the workspace root when there is one (hoisted packages resolve normally), and inside the Root Directory when the repository root has no
package.json, - 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.
rootDir is honored by every buildpack: Node, Python, PHP and a Dockerfile in a subdirectory.
There is only ever one faable.json, and it lives at the repository root — startCommand, buildCommand and the rest are read from there too, not from inside the Root Directory.
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: the newest supported minor (3.14 today) for a new app. An app that has deployed keeps the minor of its first successful deploy — it never changes under you; pin one of the versions below to move.
A requires-python floor like >=3.11 resolves to the newest supported minor that satisfies it (an app that has already deployed keeps its minor while it satisfies the floor).
Supported Python minors: 3.10, 3.11, 3.12, 3.13 and 3.14 — 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.
🐘 PHP Projects
[!TIP] Deploying Laravel or a plain PHP site? Deploy a PHP App walks through both end to end, including the environment a stock Laravel needs.
Detected by composer.json, or — with no composer.json at all — by any .php file in the repository root or in public/. Your app runs on Faable’s shared PHP runtime: Apache with mod_php, so your .htaccess rules work as written (mod_rewrite, mod_headers and mod_expires are enabled).
A minimal repo the builder accepts is exactly the one you already have:
my-site/
├── index.php ← triggers PHP detection
├── login.php
└── css/style.cssDocument Root
The builder points Apache at the first of these that holds an index.php, and falls back to the repository root:
| Layout | Document root |
|---|---|
public/index.php (Laravel, Symfony, Slim) | public/ |
public_html/index.php | public_html/ |
web/index.php | web/ |
.php files in the repository root | the repository root |
When the repository root is the document root, everything beside your PHP is web-reachable — so the runtime denies dotfiles (.env first of all), .sql/.sqlite/.db/.log/.ini/.sh/.yml files, composer.json/composer.lock, package.json, faable.json, Dockerfile, Procfile, and the vendor/, node_modules/ and .git/ directories. Directory listings are off. Move anything that isn’t web content out of the document root anyway — a public/ layout is the safe default.
Dependencies
With a composer.json, the builder runs:
composer install --no-dev --optimize-autoloader --no-interaction --no-progressYour scripts run (Laravel’s package:discover, Symfony’s cache:clear), so vendor/ ships with the app. A buildCommand in faable.json replaces that install entirely. Without a composer.json nothing is installed — plain PHP deploys as it is, and the build log says so.
PHP Version
First match wins:
.php-versionconfig.platform.phpincomposer.jsonrequire.phpincomposer.json— the platform default is used whenever the constraint allows it, otherwise the highest supported version that satisfies it- Default:
8.4for a new app; an app that has deployed keeps the minor of its first successful deploy.
Supported PHP versions: 8.2, 8.3 and 8.4. A pin outside that list fails at detection with the supported list, rather than building an app nothing can run.
Extensions
The runtime ships bcmath, exif, gd, intl, mysqli, opcache, pdo_mysql, pdo_pgsql and zip, on top of what the official PHP image includes (curl, mbstring, openssl, session, sqlite3/pdo_sqlite, xml…). Run php -m in a deployed script to see the exact set. An app that needs more ships its own Dockerfile (Hobby or Pro).
Defaults worth knowing: memory_limit 256M, max_execution_time 60s, upload_max_filesize and post_max_size 32M, and display_errors is off — errors go to the deploy logs (faable deploy logs), never to your visitors.
What PHP Apps Have to Bring Themselves
Two things trip up PHP projects more than any other stack, and neither is a build error — the deploy succeeds and the app misbehaves at runtime:
- There is no database beside your app.
new mysqli("localhost", "root", "", …)— the XAMPP/WAMP default — has nothing to connect to. Use a managed MySQL or PostgreSQL database and read the credentials from environment variables (faable deploy secrets set DB_HOST=…). The build log warns when it spots a localhost connection in your sources. - The filesystem is ephemeral. Uploads and generated files live in the running container only: they are lost on restart, on sleep/wake and on every deploy, and two instances never see each other’s files. Conventional write targets (
uploads/,storage/,writable/,var/,bootstrap/cache/…) are made writable at start-up for caches and temporary work — put anything that must survive in object storage or a database.
WordPress is refused at detection for exactly those two reasons, with an explanation instead of a broken site.
Start Command
By default the app is served by Apache from the detected document root. A startCommand in faable.json overrides it completely — use it for an app that starts another way, e.g.:
{ "startCommand": "php artisan queue:work" }📄 Static Sites
No package.json, no framework, no build step — just an index.html and the files beside it:
my-site/
├── index.html ← triggers static detection
├── style.css
├── script.js
└── assets/That deploys as it is. Your files are served directly by Faable, with no container: no language runtime boots, nothing is installed, there is no cold start, and the deploy is over in seconds. It is the same site you would open with file://, on a URL.
This is the last buildpack detection tries, and deliberately so: an index.html is the weakest signal in the repository — every framework ships one. Any manifest (package.json, requirements.txt, composer.json) or a Dockerfile claims the repo first, so a React project keeps building as React and only a repo nothing else can build lands here.
Document Root
The builder serves the first of these that holds an index.html:
| Layout | Served directory |
|---|---|
public/index.html | public/ |
dist/index.html | dist/ |
build/index.html | build/ |
_site/index.html | _site/ |
index.html in the repository root | the repository root |
A build output directory wins over the repository root, so a repo carrying a committed build in dist/ ships only that directory — the sources beside it never leave the builder. When the repository root is the document root there is nothing to separate, so the whole repository is published: every file becomes a URL, not just the pages you link to. The build log says so, and names any .env-style file it finds. Keep secrets out of a repo you deploy this way.
docs/ is not a document root: it is GitHub Pages’ convention, but it is also where most repositories keep documentation for a project that is not a website. Point rootDir at it if that is what you want to publish.
Single-Page Apps
By default an unknown path returns 404 — correct for a multi-page site, wrong for a client-side router. Turn on the rewrite-to-index.html fallback in faable.json:
{ "static": { "spa": true } }You rarely need this: a React, Vue or Angular repo has a package.json, so it builds with the Node.js buildpack, which already knows whether its framework wants an SPA fallback. This switch is for a built SPA committed to the repository with no manifest beside it.
What You Don’t Get
There is no server, so there is nothing to run server-side code with: no PHP, no API routes, no $PORT to listen on, no environment variables reaching your pages (a static file is served byte-for-byte, so a secret in your JavaScript is a public secret). Files with a content hash in their name (app.3f9a2c1d.js) are cached for a year; HTML and every other file are revalidated on each visit, so a change you deploy shows up immediately. If your site needs any of that, it needs a real runtime: add a package.json, a Python manifest, or a Dockerfile.
🐳 Dockerfile Projects
[!IMPORTANT] Available on the Hobby and Pro plans. On Free, apps build with the managed Node, Python and PHP buildpacks — see Docker and bring-your-own-image for what that means for your repo.
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.
🔢 Runtime Versions
Node.js, Python and PHP apps run on a version Faable resolves at build time. Three rules decide it:
- A version pinned in your repository always wins:
engines.nodeinpackage.json,runtime.txtor.python-version,.php-versionorconfig.platform.phpincomposer.json. - Otherwise, your app keeps the version of its first successful deploy. It never changes under you when Faable adds a newer one.
- A brand-new app gets the newest supported version (Node.js: the newest LTS).
Security patches within that version still arrive with each platform update — only the minor (Python, PHP) or major (Node.js) stays fixed.
Updating to a newer version
When a newer version is available, the app page shows it next to the runtime with an Update button. Updating rebuilds your current deployment from the same source on the new version:
- The deployment that is live keeps serving until the new build is ready — nothing goes down while it builds.
- If the build fails, your app stays on its previous version, for this deploy and the next ones, and the page tells you the update failed. The failed build’s log is in your deployment list.
If your repository pins the version, the button is disabled and the page names the file that pins it: change the version there and push instead.
Things worth knowing
- Removing a version pin from your repository does not move you to the newest version. Your app goes back to the version Faable keeps for it — the one of its first deploy, which is often the version you had pinned. Use Update after removing the pin to move.
- The newest version is not always the right one. Dependencies pinned to an exact version may not ship ready-made builds for a Python release that is only a few months old —
pydantic==2.11.7, for example, has none for Python 3.14 and fails to install there, while 3.13 works. If an update fails, pick the previous version, or upgrade those dependencies first. - Old versions are retired with notice. When a version reaches its end of life, every owner of an app still on it gets an email 90 days before the date and a reminder 30 days before, and the app page shows the date. On that day the version stops building; apps that are already running keep running.
🔌 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 },
"static": { "spa": false }
}| Field | Purpose |
|---|---|
app_id / app_slug | Which Faable app this repo deploys to (written by faable deploy link). |
buildpack | Force a buildpack (php, node, python, docker, static) instead of auto-detection. docker requires Hobby or Pro. |
rootDir | Monorepo Root Directory — the subdirectory the app builds from. This is where you set it; a per-app Root Directory (support-configured, for several apps in one repo) takes precedence when present. |
buildCommand | Node: build step used when package.json has no build script (the build script wins otherwise). Python and PHP: 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. |
static.spa | Static sites only: set true so unknown paths rewrite to index.html (client-side routers). Defaults to false — unknown paths 404. |
❓ 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.
Can I deploy a PHP app that needs MySQL?
Yes — but the database has to live somewhere else. Faable runs your PHP app, not a database beside it, so a new mysqli("localhost", "root", "", "my_db") copied from a XAMPP project connects to nothing. Point it at a managed MySQL or PostgreSQL provider and read the credentials from environment variables:
$db = new mysqli(getenv('DB_HOST'), getenv('DB_USER'), getenv('DB_PASSWORD'), getenv('DB_NAME'));Then faable deploy secrets set DB_HOST=… DB_USER=…. The build log warns you when it finds a localhost connection in your sources, so you find out at deploy time rather than from a blank page.
Can I deploy WordPress?
Not yet, and the deploy says so instead of shipping a broken site: WordPress needs a MySQL database beside the app and a persistent filesystem for wp-content/uploads, and Faable gives an app neither.
Can I deploy a plain HTML site with no framework and no package.json?
Yes. A repository that is an index.html plus its CSS, JavaScript and images deploys as it is — the static buildpack picks it up and Faable serves the files directly, with no build step, no manifest to add and no container. That covers a portfolio, a landing page, a CV, a course project, or a site built by hand or by an AI tool. If the site lives in a subdirectory, point rootDir at it. See Static Sites.
Do I need a server (Express, serve) to deploy my React or Vite app?
No. For a single-page app built with Vite, Create React App, Vue or Angular, Faable serves the build output itself, with SPA fallback and no container — see Static Frontends. If your repo has a server.js whose only job is express.static('dist') plus returning index.html, delete it and the start script that runs it: the site gets faster (no process to boot) and nothing changes for your routes. Keep a server only when it does real work — an API, server-side rendering, auth.
Why is my static site 404ing on every route except the homepage?
Because a static site serves files, and the file for that route does not exist. That is correct for a multi-page site and wrong for a single-page app with a client-side router — turn on the fallback with { "static": { "spa": true } } in faable.json and unknown paths rewrite to index.html.
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 if you are on Hobby or Pro.
My deploy says Docker builds “require a paid plan” — what now?
That is a plan entitlement, not a build error: Docker and Dockerfile builds (and deploying a prebuilt image) are available on Hobby and Pro. Nothing is wrong with your repository and there is no build log to read — the platform declined the build instead of running it.
On the Free plan, add the manifest for your stack at the repository root and the managed buildpack takes over: a package.json for Node.js, a requirements.txt or pyproject.toml for Python, a composer.json for PHP. If your repo already has one and a Dockerfile, detection prefers the manifest, so it deploys on Free as-is. A PHP repository with no composer.json is the one case a manifest cannot rescue: plain .php files are a fallback signal and lose to a Dockerfile, so remove the Dockerfile or add a composer.json. Either way, drop any "buildpack": "docker" from faable.json — forcing it is subject to the same plan requirement. Otherwise, upgrading to Hobby builds your Dockerfile verbatim. Full comparison: Docker and bring-your-own-image.
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 and 26). Without it, your app keeps the major of its first successful deploy, and a new app starts on the newest LTS (24 today). See Runtime Versions for how to move to a newer one.
How do I deploy a single app from a monorepo?
Add a faable.json at the repository root declaring the subdirectory to build from:
{ "rootDir": "apps/api" }The app builds from that subdirectory. Dependencies install at the workspace root when the repository root has a package.json (so hoisted packages and shared workspace libraries resolve normally), and inside the subdirectory when it doesn’t — a repo that is just client/ + server/ works without a workspace. Keep the faable.json at the repository root: it is the only one read. One Faable app per deployable package. See Monorepos (Root Directory) for the details.
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, PHP & Laravel.
Last updated on