Skip to content

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
hotlong merged 1 commit into
mainfrom
claude/docs-deploy-github-actions
Oct 12, 2026
Merged

hotlong merged 1 commit into
mainfrom
claude/docs-deploy-github-actions

Conversation

@hotlong

@hotlong hotlong commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Why

The Vercel team object-stack is billed ~$125 per cycle, and 97% of that is Build CPU Minutes. This repository's docs project (objectstack, Root Directory apps/docs) costs ~$70 a month of it. Measured on 2026-10-12:

  • ~533 deployments a day are Git-integration PREVIEW deployments for claude/*, gh-readonly-queue/*, board-archive* and changeset-release/* branches. Each one clones the monorepo, runs scripts/vercel-ignore-docs.sh for ~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.
  • ~34 production builds a day come from pushes to 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

  • Adds "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.
  • Removes ignoreCommand, which only ever ran inside a Git deployment. scripts/vercel-ignore-docs.sh and 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 / installCommand are unchanged: vercel build runs them, and ci.yml's Build Docs gate still reads buildCommand from this file and runs it on every docs PR.

.github/workflows/docs-deploy.yml (new)

  • Triggers: schedule every six hours at 43 0,6,12,18 * * * UTC, plus workflow_dispatch. There is no push trigger and no paths list to maintain. The docs refresh at most six hours behind main, and "Run workflow" on the workflow's Actions page publishes main immediately. 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.
  • Runner: ubuntu-latest, timeout-minutes: 30. ci.yml's forced Build Docs takes ~4 min on this runner class.
  • Steps: checkout → Node 22 and pnpm (the repo's own setup-pnpm action, and the pnpm store cache key ci.yml uses) → pnpm install --frozen-lockfile → vercel pull --yes --environment=production → vercel build --prod → assert apps/docs/.next/BUILD_ID and .vercel/output/config.json exist → vercel deploy --prebuilt --prod → write the deployment URL to the run summary.
  • CLI pinned: npx --yes vercel@60.1.3. Env VERCEL_ORG_ID=team_06nvlGKZCkwhzdXmRdxeCNnB and VERCEL_PROJECT_ID=prj_KbPkuEVCj8viErlaC0wBUgMIYZWe are identifiers, not secrets. The token comes from secrets.VERCEL_TOKEN.
  • Run from the repository root, not apps/docs. In vercel/vercel's CLI source, vercel build builds <cwd>/<rootDirectory> and writes <cwd>/.vercel/output, and vercel deploy checks that <cwd>/<rootDirectory> exists before uploading <cwd>/.vercel/output. Run from apps/docs, the project's Root Directory setting would resolve to apps/docs/apps/docs. From the root, the build runs vercel.json's commands with apps/docs as cwd, which is exactly what Build Docs runs.
  • TURBO_FORCE=true on 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 of main, so a canceled run loses nothing a later run doesn't deliver.
  • Failure direction: red, never skipped green. Each of these fails the run with an ::error::: a missing VERCEL_TOKEN, a workflow_dispatch from any ref other than main (that would otherwise publish a branch to production), and a build that exits 0 without output.

Secret: in place

VERCEL_TOKEN exists as an objectstack-ai organization secret (updated 2026-10-12T03:10:52Z), and GET /repos/objectstack-ai/objectstack/actions/organization-secrets lists it, so this repository can see it. secrets.VERCEL_TOKEN resolves 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: schedule and workflow_dispatch only see the workflow once it is on the default branch, and this job refuses any ref but main. So the first real run is the verification:

  1. gh workflow run docs-deploy.yml --repo objectstack-ai/objectstack --ref main
  2. gh 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.
  3. On Vercel, that deployment should be READY, marked Production, with source CLI, and the production domain should serve it.
  4. Branches keep being pushed all day. Confirm the objectstack project's deployment list shows no new Git-triggered deployments after the merge commit. This is the git.deploymentEnabled: false half.

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 build with 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: false is already observable on this PR. Vercel reads vercel.json from 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 same main commit and so still carrying the old vercel.json, got the usual Vercel: pending "Vercel is deploying your app" (03:35:51Z) followed by success "Canceled by Ignored Build Step" (03:36:40Z), which is exactly the billed, canceled preview this PR removes.
  • actionlint 1.7.12, with shellcheck 0.11.0: clean on docs-deploy.yml.
  • The repo's workflow-scanning gates were run locally on this branch, all exit 0: 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, plus check-self-test-wired, check-step-collectors, check-whole-set-label-write, check-aggregator-roster and check-ci-filter-parity. check-self-test-workflow-commands reds identically on untouched main (a ::group:: line printed by scripts/ci/select-shard-timings-run.mjs --self-test), so that red is not from this diff.
  • No full docs/monorepo build was run locally. Build Docs on this PR runs the production build command.

Notes

Clause-②: no


🤖 Generated with Claude Code

…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>
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 12, 2026
@github-actions github-actions Bot added size/m ci/cd documentation Improvements or additions to documentation labels Oct 12, 2026
@hotlong
hotlong added this pull request to the merge queue Oct 12, 2026
Merged via the queue into main with commit 4426d9d Oct 12, 2026
42 checks passed
@hotlong
hotlong deleted the claude/docs-deploy-github-actions branch October 12, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant