Serverless App Services and deployments
The build pipeline
What happens between a push and a ready deployment, when a push is skipped, and what the build detects and caches on its own.
What starts a build
A push that creates or updates a branch creates one deployment for every Serverless App Service linked to the repository. Pushing tags and deleting branches don't deploy.
The deployment's target is production when the branch is the Serverless App Service's production branch, and preview otherwise. Redeploys keep the target of the deployment they rebuild, and always build.
Skipped pushes
A push builds a Serverless App Service only when something that affects it changed. Otherwise the Serverless App Service gets a deployment with status skipped, and its previous deployment keeps serving.
A push is skipped when all of these hold:
- The branch has a
readydeployment to compare with. A new branch's first push is compared with the production branch's newestreadydeployment instead (only files count then, since previews have their own settings), so a branch in a monorepo that doesn't touch a Serverless App Service doesn't build it. - The build inputs outside git are the same as that deployment's: target, framework, root directory, install and build commands, the environment variables for the target, and the linked databases and buckets. Saving a variable again counts as a change, even with the same value.
- No file changed between that deployment's commit and the pushed commit that affects the Serverless App Service:
- anything under the Serverless App Service's root directory, including its
si.json; - an install file in a directory above the root directory:
package.json, a lockfile (pnpm-lock.yaml,yarn.lock,package-lock.json,npm-shrinkwrap.json,bun.lock,bun.lockb),pnpm-workspace.yaml,.pnpmfile.cjs,.npmrc,.yarnrc,.yarnrc.yml, or anything under.yarn/orpatches/; - a path matching one of the root directory's
si.jsonwatchpatterns.
- anything under the Serverless App Service's root directory, including its
When the change list can't be read, or si.json can't be read, the push builds. Serverless App Services whose root directory is the repository root build on every change.
A skipped deployment records the deployment it reused and the reason; a built one records why it built (for example changed apps/web/page.tsx or first deployment of this branch).
Where it runs
Builds run on the platform, one per deployment, on ARM (Graviton) Linux with 4 GB of memory and Node.js 22. Each build gets its own short-lived credentials (one hour) that reach only its own repository, its own deployment's files and its Serverless App Service's build cache: one build can't read or overwrite another deployment's output.
The build log marks each step with a ::step line:
| Step | Does |
|---|---|
source | Checks out the commit. |
restore | Restores the dependency and Next.js caches. |
install | Installs dependencies. |
build | Builds the app for its framework. |
upload | Uploads static files and the server bundle. |
save | Saves the caches for the next build. |
done | Finished; publishing starts. |
The deployment's page in Cloud shows how long each step took and whether the caches hit.
Install
The build installs from the nearest directory with a lockfile, starting at the root directory and going up to the repository root, so a workspace member installs from its workspace root:
| Lockfile | Package manager | Runs |
|---|---|---|
pnpm-lock.yaml | pnpm | pnpm install --frozen-lockfile |
yarn.lock | Yarn | yarn install --immutable (Yarn 2+), or yarn install --frozen-lockfile (Yarn 1) |
bun.lock or bun.lockb | Bun | bun install --frozen-lockfile |
package-lock.json or npm-shrinkwrap.json | npm | npm ci |
none, but package.json in the root directory | npm | npm install |
The package manager version comes from the packageManager field of that directory's package.json (for example "pnpm@9.15.4"). Without it, pnpm's version follows the lockfile format (7, 8 or 9), Yarn is 4 for a Yarn 2+ lockfile or .yarnrc.yml and 1 otherwise, Bun is the latest 1.x, and npm is the one that ships with Node.js. The package manager is on PATH for the build, so Next.js builds of Bun Serverless App Services run with Bun.
An install command set on the Serverless App Service replaces this step and runs in the root directory.
Build
Next.js:
- Without a
next.config.*file, an emptynext.config.mjsis created (OpenNext needs one). - OpenNext (
@opennextjs/aws3) builds the app and packages the server function. - Prerendered pages seed the deployment's incremental cache.
Static:
- The Serverless App Service's build command runs if it has one; otherwise the package manager's
run buildruns whenpackage.jsonhas abuildscript. - The output directory is the Serverless App Service's setting, or else the first of
dist,outandbuildthat exists, or else the root directory itself.
Node.js server (Express, Fastify, Hono, Koa or any server that listens on process.env.PORT):
- The Serverless App Service's build command runs if it has one; otherwise
run buildwhenpackage.jsonhas abuildscript. - The start command is the
startscript when it's a plainnode …command (for examplenode dist/server.js), elsenode <main>frompackage.json, else the first ofserver.js,index.js,app.js(or.mjs) in the root directory. Without any of these the build fails and says so. - The install directory (the app and the
node_modulesit uses) is packaged without.gitas the server function. Lambda takes up to 50 MB zipped; a larger bundle fails the build. Nothing is uploaded as public files: the server serves its own.
Caches
Builds of a Serverless App Service share two caches, kept separately for production and for each preview branch: production builds restore and save only production's, so nothing a preview build writes ever reaches production, and a preview branch restores only its own. Previews fall back to production's while their branch has none.
| Cache | Holds | Used |
|---|---|---|
| Dependencies | The package manager's store, for one lockfile and package manager version. | Restored before install. A changed lockfile restores the Serverless App Service's newest archive for the same package manager and saves a new one. |
| Next.js | .next/cache of the root directory. | Restored with the dependencies and saved after the upload. |
Archives over 1 GB aren't saved. Caches expire 14 days after they were last written; an archive still in use is refreshed after 7. A cache that can't be read is ignored and the build installs from scratch.
Upload
- Files under
_next/static/are uploaded withCache-Control: public,max-age=31536000,immutable. - Every other file is uploaded with
Cache-Control: public,max-age=0,must-revalidate. .git/,.next/andnode_modules/are never uploaded.si.jsonin the root directory is saved with the deployment (see si.json).
Environment during the build
The build sees the Serverless App Service's environment variables for the deployment's target, so values such as NEXT_PUBLIC_* are compiled in. It also sees DEPLOYMENT_ID, PROJECT_ID and ORG_ID.
Platform variables (SI_*, linked storage, the cron secret) are set on the server function only; they aren't available while building, or to static sites.
When a build fails
The deployment's status becomes error with a short reason:
| Reason | Meaning |
|---|---|
| Build could not start | The build couldn't be started. Push again or redeploy. |
| Build failed | A step exited with an error. The build logs show which. |
| Deploy failed | The build succeeded but publishing didn't. |
A build stopped before it finished leaves the deployment canceled.