Docs

How builds work

Know when Fortly installs and builds your project, what it can build, how to force or skip a build, and how to read a build that failed.

Updated October 12, 2026
On this page
  1. When a build runs
  2. Force or skip a build
  3. What a build does
  4. Package managers
  5. What Fortly can build
  6. Static sites
  7. Node servers
  8. Monorepos
  9. Dockerfiles
  10. Read the build log
  11. When a build fails
  12. Related

Fortly builds a release only when it has to, and the rule is one fact about your files: are the dependencies there? A project that arrives without node_modules could not run as it is, so Fortly installs its dependencies, runs its build script and packages the output. A project that arrives with them, or a folder of static files, is deployed as it arrived. You can override the rule per deploy with the x-fortly-build header.

When a build runs

What you deployWhat happens
A push or deploy from a linked GitHub branchBuilt. A repository never carries node_modules.
An upload with a package.json and no node_modulesBuilt.
An upload with a package.json and a node_modules folderDeployed as it arrived. Nothing is installed or run.
A folder with an index.html and no package.jsonServed as a static site. Nothing to build.

Builds run on Fortly's build service, never in the place your app is served from. A build that is still running does not hold your request open: the deploy answers 202 Accepted straight away and the build continues behind it. See Deploying your code.

Force or skip a build

Send x-fortly-build with a deploy to decide for yourself. It works on POST /projects/{name}/deploys and POST /projects/{name}/github/deploy.

ValueEffect
skip (also off, none, false)Never build. The archive is deployed as it arrived.
force (also always)Build even though node_modules is present. Needs a package.json; without one there is nothing to install, and nothing is built.
Anything else, or no headerThe default: build when the dependencies are missing.
Terminal
# A Vite site you built locally: dist/ is ready, package.json is in the archive
curl -X POST https://api.fortly.com/projects/my-app-ada/deploys \
  -H "Authorization: Bearer $FORTLY_TOKEN" \
  -H "x-fortly-build: skip" \
  --data-binary @release.tgz

skip only works for output that can be served as files. A server app — Next.js, Nuxt, Express and the rest — has to be built by Fortly to run here. Skipping its build fails the deploy with: This upload looks like it needs a server, but it wasn't built through Fortly's own build pipeline. Send the source without node_modules instead, and let Fortly build it.

What a build does

Install the dependencies

Fortly installs with the package manager your project uses, from its lockfile when there is one. If the lockfile has drifted from package.json, the strict install fails and Fortly falls back to a normal install, saying so in the log.

Run your build script

The build script from package.json runs. A project that builds to static files or uses a server-rendering framework needs one. For an Express, Koa, NestJS, Fastify, Hono or Elysia app it is optional, and runs only if it is there.

Package the output

The output becomes either a set of static files or a Worker that serves your app, depending on the framework. See What Fortly can build.

The build receives your project's environment values that are scoped to builds — build or both — so a framework can bake public configuration into its output. Values scoped to runtime are not given to the build. See Environment and secrets.

A build runs on a restricted network: it can download packages, and nothing else on the network is reachable from it. A build that tries to fetch other addresses fails.

Package managers

Fortly picks the package manager from the packageManager field in package.json first, then from the lockfile present, and uses npm when neither says.

ManagerChosen whenInstall with a lockfileFallback, or without a lockfile
npmpackage-lock.json, or nothing elsenpm cinpm install
pnpmpnpm-lock.yamlpnpm install --frozen-lockfilepnpm install
Yarnyarn.lockyarn install --immutableyarn install
Bunbun.lock or bun.lockbbun install --frozen-lockfilebun install

If an archive carries lockfiles for more than one manager, the chosen one wins and the log notes the others are ignored.

What Fortly can build

Fortly detects the framework from the dependencies in package.json. What it detects decides what the build produces.

Served as a Worker — for frameworks that render on the server or are servers:

FrameworkCondition
Next.js, Nuxt, Nitro, SolidStart, AnalogNone
TanStack Start, React Router (framework mode)None
Express, Koa, NestJS, FastifyNone. See Node servers.
Hono, ElysiaNone
Astro@astrojs/cloudflare is a dependency
SvelteKit@sveltejs/adapter-cloudflare is a dependency
Qwikadapters/cloudflare-pages/vite.config.ts exists (written by qwik add cloudflare-pages)
Angularangular.json builds a server bundle with ssr.experimentalPlatform set to "neutral"

Served as static files — any other project with a package.json, such as a Vite, React, Vue or Svelte app or Create React App, and Astro, SvelteKit, Qwik or Angular projects without the condition above. Its build must write an index.html into one of the folders listed under Static sites. Remix is deployed as its static output only.

Refused — a Python, Rust, Go, Ruby or PHP service. Fortly has no runtime to serve it, so the deploy fails and the live release is left as it was.

Static sites

For a project that builds to files, Fortly takes the first of these folders that holds an index.html as the site: out, dist, build, .output/public. An upload that arrives already built is also recognised from _site.

A single-page app gets a fallback: a path with no file behind it is answered with index.html, so client-side routes work on a refresh. That applies to React, Vue, Svelte, Angular, Vite and Create React App projects, and to any site whose output is one HTML page plus a bundle folder (assets, _framework, _app, _nuxt, static or js). A multi-page generated site gets real 404s instead.

Node servers

For an Express, Koa, NestJS, Fastify, Hono or Elysia app, the build runs your build script if you have one, then looks for the file to serve in this order, taking the first that exists and ends in .js, .cjs or .mjs:

  1. main in package.json
  2. The file your start script runs with node (the last file on that command line, skipping --require and --import preloads)
  3. dist/main.js, dist/index.js, dist/server.js, build/index.js
  4. server.js, app.js, index.js, src/index.js, src/server.js

A public or static folder is served as files alongside your app. How the server is reached is covered in Node servers and ports.

Monorepos

When the root package.json names no framework, Fortly looks one level inside apps/, packages/, sites/ and services/. If exactly one of those folders holds an app with a framework, that app is what gets built and deployed. If more than one does, Fortly does not choose: create a project per app, or keep one app in the repository.

Dockerfiles

Fortly does not build container images. A Dockerfile in your project is read for hints — the command it starts, the port it exposes, where a static build puts its output — and never run. To stop Fortly reading one, send {"ignore_dockerfile": true} to PATCH /projects/{name}.

Read the build log

A build writes to your project's log in two voices, which the log viewer lets you filter by source:

  • system — Fortly narrating the deploy: what arrived, what it found, and each step.
  • build — your install and build output.
Deploying an upload · 1.2 MB
Unpacked 214 files · 3.4 MB
Node.js · Next.js · npm
Building with the build service (small)
fortly-build: manager=npm adapter=next
fortly-build: installing with npm ci --no-audit --no-fund --loglevel=error
...
Built · 87 files · 5.1 MB
Uploading runtime bundle...
Validating deployment...
Deployment ready.

Lines starting fortly-build: come from Fortly's build driver; everything else under build is your tools' own output. The build's output arrives when the build finishes, as its last 200 lines, rather than line by line while it runs. Values from your environment that are 8 characters or longer are replaced with [redacted] wherever they appear.

The release record keeps what ran in its build field, such as npm ci && npm run build, and a failed release keeps a short reason in failure, so you can see why without opening the log. See Logs and metrics.

When a build fails

A failed build never touches the live release. The new release is recorded as failed, its files are discarded, and the release that was already live keeps serving.

Message containsWhat to do
installing the dependencies failedRead the install output under build. A dependency that needs network access during install, or a lockfile that cannot resolve, are the usual causes.
could not find this application's entry module — name it in package.json "main", or have "scripts.start" run it with nodeSet main, or make your start script run your server file with node.
the build finished and produced no artifact under /outYour build wrote nothing Fortly could serve. Check that it writes to out, dist, build or .output/public.
the build modified package.jsonYour build script must not rewrite package.json or package-lock.json.
a release is already being built for this project — wait for it to finishOne build at a time per project. Deploy again once the current one finishes.
Something missing or unclear? Tell us and we will fix the page.© 2026 Fortly