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 ready deployment to compare with. A new branch's first push is compared with the production branch's newest ready deployment 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/ or patches/;
    • a path matching one of the root directory's si.json watch patterns.

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:

StepDoes
sourceChecks out the commit.
restoreRestores the dependency and Next.js caches.
installInstalls dependencies.
buildBuilds the app for its framework.
uploadUploads static files and the server bundle.
saveSaves the caches for the next build.
doneFinished; 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:

LockfilePackage managerRuns
pnpm-lock.yamlpnpmpnpm install --frozen-lockfile
yarn.lockYarnyarn install --immutable (Yarn 2+), or yarn install --frozen-lockfile (Yarn 1)
bun.lock or bun.lockbBunbun install --frozen-lockfile
package-lock.json or npm-shrinkwrap.jsonnpmnpm ci
none, but package.json in the root directorynpmnpm 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 empty next.config.mjs is created (OpenNext needs one).
  • OpenNext (@opennextjs/aws 3) 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 build runs when package.json has a build script.
  • The output directory is the Serverless App Service's setting, or else the first of dist, out and build that 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 build when package.json has a build script.
  • The start command is the start script when it's a plain node … command (for example node dist/server.js), else node <main> from package.json, else the first of server.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_modules it uses) is packaged without .git as 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.

CacheHoldsUsed
DependenciesThe 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 with Cache-Control: public,max-age=31536000,immutable.
  • Every other file is uploaded with Cache-Control: public,max-age=0,must-revalidate.
  • .git/, .next/ and node_modules/ are never uploaded.
  • si.json in 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:

ReasonMeaning
Build could not startThe build couldn't be started. Push again or redeploy.
Build failedA step exited with an error. The build logs show which.
Deploy failedThe build succeeded but publishing didn't.

A build stopped before it finished leaves the deployment canceled.