# Website deployment runbook

## Current authority and observed status

The owner's 5 October 2026 request to deploy on **Cloudflare Free** supersedes the earlier GitHub Pages hosting plan. It authorizes publishing this handbook and its existing approved assets on Cloudflare while keeping `devSatym/gcp-security-handbook` private. The original application repository is never a publication or push target. Repository visibility, billing, nameservers and unrelated DNS records must remain unchanged.

Primary public URL: [https://security.devsatym.xyz](https://security.devsatym.xyz). Custom-domain DNS and HTTPS were verified at `2026-10-04T23:38:38.482202Z`: Pages domain, verification and certificate-validation statuses were active, and a real HTTPS request returned200 with TLS verification result0. The owner added `CNAME security → gcp-security-handbook.pages.dev`; direct authoritative launch1/launch2 and recursive1.1.1.1 queries confirmed it. One documented Pages validation retry was performed; no agent DNS, nameserver, billing or unrelated-record writes were made. Custom-origin build, metadata and hosted verification results are recorded separately in excluded audit receipts. DNS/TLS activation and historical Pages checks do not establish those artifact results.

First `pages.dev` publication checkpoint (**PUBLISHED_VERIFIED**): the handbook was verified at [https://gcp-security-handbook.pages.dev](https://gcp-security-handbook.pages.dev). Native production deployment `2980e038-8c47-465d-8f22-1d3e5d65feba` succeeded at `2026-10-04T22:54:59.291Z` for `48d43eaddbd22af1bf05b40a1083e6944856edbf`. Its actual hosted browser suite passed31 cases (25 Chromium,6 Firefox), with zero failures, skips or retries; all14 PNG decode/full-resolution checks passed. This first real production/browser checkpoint is separate from local validation and historical cloud evidence.

The later `pages.dev` checkpoint deployed `f88eade085b24cb9ddf317eeb7dac0e2774e0943` automatically via `github:push` as production `e827c5e3-ed4e-4bff-963f-b240d44da55a`; production HTTP passed170 checks with zero errors. Its targeted browser update passed one existing Chromium author/navigation/Axe case plus10 supplemental live-card assertions. Preview `4a99a44f-868c-47be-9132-dbbca2872059` at the same commit also passed170 HTTP checks; GitHub run37243283187 succeeded. Those receipts remain under excluded `engineering/audits/`, separately from the first48d checkpoint and the new custom-domain promotion. An audit-only receipt commit may differ from the deployed content revision.

Native Git preview `cc71014e-4c1c-4281-beb9-26d9c7592593` succeeded at `2026-10-04T22:57:24.971Z` for branch `docs/cloudflare-preview-20261005`, the same commit and trigger `github:push`, at [the preview URL](https://cc71014e.gcp-security-handbook.pages.dev). This establishes the automatic preview build path; preview browser verification is separate. Production auto-deployments are now confirmed enabled, with all preview branches, PR comments disabled and scoped watch paths retained.

Earlier OAuth refresh failure, HTTP401/code8000011 Git-installation failure and HTTP500/code8000000 creation failure remain history. OAuth was verified at `2026-10-04T22:11:34Z`; simplified Git-source creation succeeded at `2026-10-04T22:49:55.869626Z`, with the expected private source/main and no initial deployment. Configuration succeeded before the explicit first native Git build. No billing, repository-visibility, unrelated DNS or original source-project change is recorded.

First Cloudflare checkpoint commit `48d43eaddbd22af1bf05b40a1083e6944856edbf` passed GitHub validation [run37241732971](https://github.com/devSatym/gcp-security-handbook/actions/runs/37241732971). Earlier private delivery `1cf3ceed21144feacaf3cb2d1be0c9ffb0796fc1` and successful run37238644633 remain historical. CI success, native deployment and hosted browser results are separate observations.

The earlier private delivery is commit `199e811f490d0d6fe02fe5390e108d7687caef6e`. GitHub validation [run 37235383111](https://github.com/devSatym/gcp-security-handbook/actions/runs/37235383111) completed successfully for that exact commit, from `2026-10-04T21:15:50Z` to `21:19:32Z`. Its local checkpoint passed ten unit, eighteen content/workflow and 93 browser cases across three profiles. These are historical website checks, not observations of the new Cloudflare integration or a hosted site. Cloudflare-specific local and hosted results must receive separate records.

All fourteen historical evidence PNGs retain their original bytes and disclosed terminal/account metadata. The owner's later Cloudflare instruction authorizes the existing approved assets; it does not select optional pixel edits. Preserve source hashes, capture/revision boundaries and attribution. Hosting this static handbook performs no GCP/Azure authentication or infrastructure operation and does not establish that a historical cluster exists today.

The pre-publication local Cloudflare checkpoint passed14 unit,18 content/workflow and155 browser cases across Cloudflare production/preview samples and three compatibility profiles. Each output had49 HTML pages,4,335 links/assets,22 diagrams and162 files with no audit errors. Preserve fingerprint `1e8cb087c90777e891f640f2442e03c4105492267a0aa963a92b72d51655ed1d` and its separate audit records. The samples were local coordinates, not assigned hosts. This retained checkpoint is not silently reused for subsequent HTTP-helper source changes.

## Prepare and inspect locally

Use Node `22.20.0` from `.nvmrc` and the committed lockfile. From the handbook root:

```bash
cd website
npm ci
npx playwright install --with-deps chromium firefox
npm run validate
```

`validate` runs hygiene, Astro/type, unit/content checks and independent custom/GitHub/legacy production builds, output audits and Chromium/Firefox tests. Each profile keeps its own artifact and audit records; the custom-root output is restored afterward. Keep the earlier logs unchanged. Development uses `npm run dev` on loopback; local file saves do not update the deployed site.

Historical source hashing remains an explicit read-only operation:

```bash
SOURCE_REPO_PATH=/path/to/source-checkout npm run audit:sources
```

That checkout must contain GCP object `cbbc807c0c150e106affa89fbb1b9e8349005749` from `devSatym/gcp-supply-chain-security`. It supplies historical Git objects only. Routine website builds do not need the source checkout or cloud credentials. `npm run audit:performance` records separate local lab measurements; it cannot establish hosted performance.

## Connect the private repository to Pages

Native Git integration builds on pushes to the connected private GitHub repository. First grant the **Cloudflare Workers and Pages** GitHub App access to this handbook repository, then create a Git-integrated Pages project in the intended Cloudflare account. Prefer access to the selected repository. Wrangler OAuth authorizes Cloudflare API operations; it does not grant the GitHub App repository access. [Git integration](https://developers.cloudflare.com/pages/get-started/git-integration/), [GitHub integration](https://developers.cloudflare.com/pages/configuration/git-integration/github-integration/)

The scoped login procedure, already completed and verified at the current checkpoint, is:

```bash
wrangler login --scopes account:read user:read pages:write
```

The owner completes the OAuth browser interaction. Keep tokens and refresh credentials out of chat, Git, logs and public reports. Confirm the account and Pages scope through read-only discovery after login. Do not use a global API key or change billing to bypass an access failure. [Wrangler login](https://developers.cloudflare.com/workers/wrangler/commands/general/#login)

The owner's later request for an install/login script is implemented at repository-root `scripts/setup-cloudflare.sh`. Run `bash scripts/setup-cloudflare.sh` from the repository root. It requires Node22+ and npm, installs pinned Wrangler4.147.0 into a separate user-local tools folder, starts device login with the same scoped access and checks the saved account. `--install-only` omits sign-in; `--status` checks access. The script performs no deployment/DNS operation and requires the owner's browser approval for login. Device login avoids a localhost callback and can be approved on another device. [Wrangler installation](https://developers.cloudflare.com/workers/wrangler/install-and-update/), [Device login](https://developers.cloudflare.com/workers/wrangler/commands/general/#use-wrangler-login-without-a-local-callback-server)

Do not initially run `wrangler pages project create`: it creates a Direct Upload project without a Git source, which cannot later become Git-integrated. The supported API route is `POST /accounts/{account_id}/pages/projects`; its source schema accepts GitHub owner/repository identity and deployment controls after the independent GitHub grant exists. There is no `installation_id` field in that schema. The simplified Git-source request has now created the expected Pages project, establishing usable Git integration for that creation. Earlier Git-installation and internal-server failures remain recorded history. [Create API](https://developers.cloudflare.com/api/resources/pages/subresources/projects/methods/create/), [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/)

| Setting | Required value |
| --- | --- |
| Source repository | Private `devSatym/gcp-security-handbook` |
| Production branch | `main` |
| Framework | Astro static |
| Root directory | `website` |
| Build command | `npm run build:cloudflare` |
| Build output directory | `dist` |
| Node version | `22.20.0` |
| Production `DOCS_SITE` | The actual confirmed HTTPS production origin |
| Base | `/` |
| Preview origin | Validated Cloudflare `CF_PAGES_URL` on non-main branches |

Production `DOCS_SITE=https://security.devsatym.xyz` is configured at `/`, with the same fallback in the preview environment. Native non-main previews continue to use their validated `CF_PAGES_URL` with noindex/robots controls. Root `website`, command `npm run build:cloudflare`, output `dist` and Node22.20.0 stay the native build settings. The custom-origin build regenerates canonical/OG/sitemap metadata. Native deployment and actual custom-origin verification are recorded separately in excluded audits; prior `pages.dev` checks do not prove the promoted artifact. [Build configuration](https://developers.cloudflare.com/pages/configuration/build-configuration/)

Initial API setup paused branch deployments and omitted production `DOCS_SITE`, then confirmed the assigned subdomain before configuring the stable origin and branch controls. The configuration and first explicit native Git build are now observed. Main pushes deploy automatically; explicit Git rebuilds use POST deployments and multipart `branch=main`, branch HEAD and no asset manifest. Inspect returned commit identity before hosted verification. Preserve initial failures separately from successful deployment records. [Get project](https://developers.cloudflare.com/api/resources/pages/subresources/projects/methods/get/), [Edit project](https://developers.cloudflare.com/api/resources/pages/subresources/projects/methods/edit/), [Create Git deployment](https://developers.cloudflare.com/api/resources/pages/subresources/projects/subresources/deployments/methods/create/)

`scripts/build-cloudflare.mjs` selects `DOCS_PROFILE=cloudflare` and runs lint, Astro/type checks, unit checks, content/workflow checks, preparation/build and the output audit. It then rejects symlinks/nonregular assets, more than 20,000 files or an asset larger than 25 MiB. It builds static output only; no upload occurs in this local command. No server adapter, Worker, Function, database or paid image service is needed.

## Continuous editing on the Free plan

The current Free allowance is 500 native builds per month, one concurrent account-wide build and a 20-minute timeout. Static sites allow 20,000 files, with 25 MiB maximum per asset; up to 100 custom domains per project. Static asset requests are free and unlimited. Functions have separate Workers quotas, and this handbook does not use them. Current account quotas and service terms apply; no unlimited build or perpetual-free promise is made. Domain renewal remains a separate registrar expense. [Pages limits](https://developers.cloudflare.com/pages/platform/limits/), [Static pricing](https://developers.cloudflare.com/pages/functions/pricing/)

1. Edit locally and run the meaningful checks for the change.
2. Push a work branch to the private handbook repository for a native preview. Inspect its routes, images, metadata and read-only GitHub browser-check results.
3. Merge reviewed changes into `main`; a successful native production build publishes the new version. Failed build results require investigation and repair.

Edit locally with `npm run dev`, push a work branch for a native preview, review it and passing GitHub checks, then merge/push `main` for an automatic production build. The existing published version stays live while the next build runs. Local saves alone do not publish. Preview and production builds consume Cloudflare Free's500-build monthly allowance; private GitHub Actions has a separate minute/artifact budget. Include numbered engineering reports and other public build inputs in watch paths; exclude generated private audit/history captures. [Watch paths](https://developers.cloudflare.com/pages/configuration/build-watch-paths/)

The Cloudflare build does not run browser cases or automatically wait for `docs-validate.yml`. That unchanged read-only workflow runs the separate three-profile browser matrix on relevant PRs and main pushes; the delivered checkpoint contained 93 browser cases. Review previews and passing GitHub checks before merging production changes. Private-repository GitHub Actions consumes the owner's separate included-minute and artifact allowances; Cloudflare's 500 native builds do not cover or promise unlimited free GitHub CI. Inspect the actual account plan and usage before increasing CI frequency. [Actions billing](https://docs.github.com/en/billing/concepts/product-billing/github-actions)

Native previews use their own origin and indexing controls; Cloudflare adds `X-Robots-Tag: noindex`. Preview URLs remain public unless Access is configured, and `noindex` is not authentication. This setup does not create an Access application. [Preview deployments](https://developers.cloudflare.com/pages/configuration/preview-deployments/)

## Route only the security hostname

Historical pre-activation record: the `security.devsatym.xyz` Pages binding returned HTTP200 at `2026-10-04T22:56:00Z`, initially initializing. At `2026-10-04T23:00:08Z`, its status remained pending with verification error "CNAME record not set". At that earlier observation, DNS confirmed `launch1.spaceship.net`/`launch2.spaceship.net` authority and no security CNAME in direct authoritative or recursive queries. The then-required owner record was `CNAME security → gcp-security-handbook.pages.dev`; DNS and TLS were not yet verified. That state is superseded by the owner-added record and active HTTPS observation above; preserve apex, MX, TXT, CAA, nameservers and other hosts. [Custom domains](https://developers.cloudflare.com/pages/configuration/custom-domains/)

Recheck the authoritative provider and exact-host record state using read-only queries:

```bash
dig NS devsatym.xyz +short
dig CNAME security.devsatym.xyz +short
dig A security.devsatym.xyz +short
```

The latest activation observation confirms Spaceship authority and the exact owner-added CNAME below. It is already present; do not add a duplicate or alter other records:

```text
CNAME   security   gcp-security-handbook.pages.dev
```

The target has no protocol, slash or repository path. Record and review any conflicting exact-host record before replacing it. Preserve apex, MX, TXT, CAA, nameservers and all other hosts. Do not configure wildcard records, future `resilience`/`aks` hosts or the root portfolio. The old GitHub account TXT challenge, `devsatym.github.io` target and Enforce HTTPS switch belong to the superseded GitHub procedure and are not Cloudflare setup steps.

DNS and certificate activation are now observed. Custom-origin build and hosted checks cover metadata, routes, search, redirects/404, original image integrity and private-file absence. Preserve old build/deployment records and record the new revision/deployment ID in excluded audits; DNS/TLS activation does not establish those artifact results.

## Verify real hosted behavior and rollback

Production HTTP verification for the first Cloudflare checkpoint passed170 checks with zero errors:48 authored HTML pages,106 static assets (including48 Pagefind fragments, JS/WASM,14 PNGs and the social image),7 custom404-body probes,2 redirects and7 Pagefind graph assets. The normalized48-page/5-chunk/2,803-term graph and postings match. The seven generated graph assets use bounded semantic comparison that accounts for page-ID ordering; this is not a claim of byte equivalence. Static assets retain exact integrity checks. Six meaningful parser/tamper fixture groups pass. Records are `audits/cloudflare-hosted-production-semantic.json`/`.log` and `audits/cloudflare-pagefind-production-first-diagnostic.json`.

The first127-check/13-error result is preserved as a resolved checker issue involving7 generated Pagefind files. Stable fragment hashes and normalized terms/postings agreed after page-ID ordering was accounted for; the strict semantic parser/comparator now verifies that bounded contract. The earlier raw result is retained rather than relabelled as a byte-identical pass.

Laterf88 production/preview checks and new custom-origin promotion checks have separate receipt boundaries. Record actual origin, revision and deployment rather than extending the first checkpoint to a new artifact.

| Status | Current interpretation |
| --- | --- |
| LOCAL_VERIFIED | Retained14 unit/18 content/155 browser pre-publication checkpoint; separate historical fingerprint |
| PUBLISHED_BROWSER_VERIFIED | Native production at48d43eadd… succeeds;31 hosted browser cases and14 PNG checks pass |
| HTTP_VERIFIED |170 checks, zero errors; bounded Pagefind graph comparison matches |
| DNS_VERIFIED | Owner-added exact security CNAME confirmed by authoritative and recursive queries |
| HTTPS_VERIFIED | Pages/verification/validation active and real custom-host HTTPS200 with certificate verification |
| PUBLISHED_VERIFIED | Retained48d/f88 pages.dev checkpoints verified; custom-domain promotion receives separate receipts |
| CUSTOM_ORIGIN_VERIFICATION | Custom-origin build/hosted receipts are recorded separately in excluded audits; no inference from DNS/TLS |

For a real production rollback, select a previously successful production deployment in Pages and verify the restored routes, search, image hashes and revision. Preview deployments are not eligible rollback targets. Restore DNS only to a reviewed previous exact-host state when that is necessary; never alter the original application or unrelated domain services. No rollback is performed by this runbook. [Pages rollbacks](https://developers.cloudflare.com/pages/configuration/rollbacks/)

## Historical GitHub compatibility checkpoint

The independent `custom` root, `github` `/gcp-security-handbook` and `legacy` `/gcp-supply-chain-security` artifacts remain compatibility tests. The legacy path is never the original application's publishing target. Each artifact belongs to one origin/base. The retained manual `docs-pages.yml` is the earlier GitHub deployment mechanism, with its private-repository guard; it is not used for the current Cloudflare setup and must not be dispatched to bypass that guard. The source and evidence baseline, initial/migration audits and earlier Lighthouse measurements remain historical records. Internal record `14-CLOUDFLARE-DEPLOYMENT.md` tracks the current setup; it is not automatically added to the public-report allowlist.
