Projects 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 project linked to the repository. Pushing tags and deleting branches don't deploy.
The deployment's target is production when the branch is the project's production branch, and preview otherwise. Redeploys keep the target of the deployment they rebuild, and always build.
Skipped pushes
A push builds a project only when something that affects it changed. Otherwise the project 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. - 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 project:
- anything under the project'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 project's root directory, including its
When the change list can't be read, or si.json can't be read, the push builds. Projects 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 project'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) |
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, and npm is the one that ships with Node.js.
An install command set on the project 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 project's build command runs if it has one; otherwise the package manager's
run buildruns whenpackage.jsonhas abuildscript. - The output directory is the project's setting, or else the first of
dist,outandbuildthat exists, or else the root directory itself.
Caches
Builds of a project share two caches:
| Cache | Holds | Used |
|---|---|---|
| Dependencies | The package manager's store, for one lockfile and package manager version. | Restored before install. A changed lockfile restores the project'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 project'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.
Planned
- Passing build variables through Parameter Store instead of build environment overrides. Coming soon