Repository navigation
ci(docs): build the docs site on a free GitHub runner every 6 hours and deploy it prebuilt; turn Vercel's Git deployments off - #22925
Merged
Conversation
…rebuilt; turn Vercel's Git deployments off Vercel bills Build CPU Minutes for every Git-triggered deployment of the docs project, including the ~533 preview deployments a day that clone the monorepo, run the Ignored Build Step script and are canceled. Measured on 2026-10-12, Build CPU Minutes are 97% of the team's invoice and this project is ~$70 of it. - apps/docs/vercel.json: `git.deploymentEnabled: false`, so no branch push creates a Vercel deployment. The `ignoreCommand` only ever ran inside a Git deployment, so it and scripts/vercel-ignore-docs.sh (with its self-test, which no workflow ran) are removed. `buildCommand` and `installCommand` stay: `vercel build` runs them, and ci.yml's Build Docs gate reads `buildCommand` from this file. - .github/workflows/docs-deploy.yml: every six hours (:43 past 00/06/12/18 UTC) and on workflow_dispatch, on ubuntu-latest (free for this public repository): `vercel pull`, `vercel build --prod`, then `vercel deploy --prebuilt --prod`, with the CLI pinned and every command run from the repository root because the project's Root Directory is apps/docs. A missing VERCEL_TOKEN, a non-main ref, or a build that left no output fails the run red; nothing skips green. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The Vercel team
object-stackis billed ~$125 per cycle, and 97% of that is Build CPU Minutes. This repository's docs project (objectstack, Root Directoryapps/docs) costs ~$70 a month of it. Measured on 2026-10-12:claude/*,gh-readonly-queue/*,board-archive*andchangeset-release/*branches. Each one clones the monorepo, runsscripts/vercel-ignore-docs.shfor ~25 s and is canceled, and is still billed: billed CPU-minutes divided by production-build wall-minutes is 12–15, more than the 8-vCPU build machine can produce alone, so the canceled ones are in the bill. That is ~40% of the project's cost.main(which gets ~87 commits a day).The maintainer's ruling, verbatim:
This repository is public, so standard GitHub-hosted runners (
ubuntu-latest) are free. The docs are now built there and uploaded to Vercel already built, so Vercel bills zero build minutes for this project.What changes
apps/docs/vercel.json"git": { "deploymentEnabled": false }(the boolean form in https://openapi.vercel.sh/vercel.json). No branch push creates a Vercel deployment any more, so nothing is cloned, canceled or billed.ignoreCommand, which only ever ran inside a Git deployment.scripts/vercel-ignore-docs.shand its self-test (which no workflow ran) are deleted with it. The failure-direction rule that script's header spelled out, that a wrong skip silently stops publishing, now lives in the new workflow's header and is enforced there.buildCommand/installCommandare unchanged:vercel buildruns them, and ci.yml's Build Docs gate still readsbuildCommandfrom this file and runs it on every docs PR..github/workflows/docs-deploy.yml(new)scheduleevery six hours at43 0,6,12,18 * * *UTC, plusworkflow_dispatch. There is nopushtrigger and no paths list to maintain. The docs refresh at most six hours behindmain, and "Run workflow" on the workflow's Actions page publishesmainimmediately. Four builds a day keep this job from competing with CI for runners. The hours stay out of the crowded 01–07 UTC nightly window and the board snapshot's 02/08/14/20; the minute is off the top of the hour. The reasoning is in the file.ubuntu-latest,timeout-minutes: 30. ci.yml's forced Build Docs takes ~4 min on this runner class.setup-pnpmaction, and the pnpm store cache key ci.yml uses) →pnpm install --frozen-lockfile→vercel pull --yes --environment=production→vercel build --prod→ assertapps/docs/.next/BUILD_IDand.vercel/output/config.jsonexist →vercel deploy --prebuilt --prod→ write the deployment URL to the run summary.npx --yes vercel@60.1.3. EnvVERCEL_ORG_ID=team_06nvlGKZCkwhzdXmRdxeCNnBandVERCEL_PROJECT_ID=prj_KbPkuEVCj8viErlaC0wBUgMIYZWeare identifiers, not secrets. The token comes fromsecrets.VERCEL_TOKEN.apps/docs. In vercel/vercel's CLI source,vercel buildbuilds<cwd>/<rootDirectory>and writes<cwd>/.vercel/output, andvercel deploychecks that<cwd>/<rootDirectory>exists before uploading<cwd>/.vercel/output. Run fromapps/docs, the project's Root Directory setting would resolve toapps/docs/apps/docs. From the root, the build runs vercel.json's commands withapps/docsas cwd, which is exactly what Build Docs runs.TURBO_FORCE=trueon the build. A fresh runner has no turbo cache and this job passes no remote-cache credentials, so nothing can be replayed today. Forcing keeps it that way if either changes, for the same reason Build Docs forces.concurrency: { group: docs-deploy-production, cancel-in-progress: true }. Every run builds the tip ofmain, so a canceled run loses nothing a later run doesn't deliver.::error::: a missingVERCEL_TOKEN, aworkflow_dispatchfrom any ref other thanmain(that would otherwise publish a branch to production), and a build that exits 0 without output.Secret: in place
VERCEL_TOKENexists as an objectstack-ai organization secret (updated 2026-10-12T03:10:52Z), andGET /repos/objectstack-ai/objectstack/actions/organization-secretslists it, so this repository can see it.secrets.VERCEL_TOKENresolves an org secret the same way it resolves a repo secret, so the workflow needs no change. Nothing in this PR creates tokens or sets secrets.Verification plan, right after merge
The deploy path cannot run before merge:
scheduleandworkflow_dispatchonly see the workflow once it is on the default branch, and this job refuses any ref butmain. So the first real run is the verification:gh workflow run docs-deploy.yml --repo objectstack-ai/objectstack --ref maingh run watch <run-id> --repo objectstack-ai/objectstack. Every step should go green through Deploy the prebuilt output to production. The run summary prints the deployment URL.objectstackproject's deployment list shows no new Git-triggered deployments after the merge commit. This is thegit.deploymentEnabled: falsehalf.If step 2 fails, production keeps serving the last deployment the Git integration made (stale, not down). Fix forward and re-run with "Run workflow". The part not exercised before merge is
vercel buildwith the pinned CLI against this Next.js 16.3 app; the build command it runs is the one Build Docs proves on this PR.Validation done
git.deploymentEnabled: falseis already observable on this PR. Vercel readsvercel.jsonfrom the pushed commit. This PR's head (91f9eda3f5) has zero commit statuses from Vercel and no GitHub deployments. The sibling PR ci(board-snapshot): upload only the records that changed to R2, not all ~41k objects every run #22930, branched from the samemaincommit and so still carrying the oldvercel.json, got the usualVercel: pending "Vercel is deploying your app"(03:35:51Z) followed bysuccess "Canceled by Ignored Build Step"(03:36:40Z), which is exactly the billed, canceled preview this PR removes.actionlint1.7.12, with shellcheck 0.11.0: clean ondocs-deploy.yml.check:node-version,check:pnpm-acquisition,check:workflow-step-name-quoting,check:workflow-status-functions,check:required-contexts,check:stall-guard-budget,check:shard-attestation,check:pm-dispatch-gates,check:pm-expected-skips,check:agent-test-spelling,check:pnpm-filter-targets, pluscheck-self-test-wired,check-step-collectors,check-whole-set-label-write,check-aggregator-rosterandcheck-ci-filter-parity.check-self-test-workflow-commandsreds identically on untouchedmain(a::group::line printed byscripts/ci/select-shard-timings-run.mjs --self-test), so that red is not from this diff.Notes
vercel,docs deploy) and found none open. This builds on the closed history of the docs-deploy cost work (docs deploy: every push to main rebuilds the docs site — 98.6% of the team's build time, $171/mo, and a 92-deep queue;turbo-ignorealone would silently stop publishing docs #12743 / build(docs): stop rebuilding the docs site on every push to main #12746, which added the ignore command removed here).skip-changeset.VERCEL_TOKENinto a GitHub Environment restricted tomain. Today any same-repo workflow can read the org secret, like every other secret here.Clause-②: no
🤖 Generated with Claude Code