GH org secrets, build-time inject
GH org secrets, build-time inject
Mechanism
- Shared tokens (GA4 measurement ID, PostHog project key, Clarity project ID, Fathom site ID, GoatCounter site code, third-party API keys) are stored once as GitHub organization-level Actions secrets on
chirag127. - Each repo's CI workflow references them via
${{ secrets.PUBLIC_GA4_ID }}etc., exposed into the build job'senv:. - Astro reads them at build time as
PUBLIC_*env vars (Astro's documented prefix for browser-exposed values). - The static output is baked with the values inlined. Deploy to CF Pages or GitHub Pages — no runtime secret store needed.
One source of truth across every app in the org. Rotate the org secret once, every CI run after that picks up the new value on the next deploy.
Alternative considered
CF Pages env vars (set per-project in the CF dashboard) — rejected because it causes per-project drift. Each app would carry its own copy of the same key, rotation becomes 22+ dashboard edits, and the GitHub Pages-hosted landing pages don't get coverage at all (different host). Org-level GH secrets unify both deploy targets under one source.
On the word "secret"
PUBLIC_* env vars in Astro are public by design — they ship to the browser as plain strings in the bundled JS. The analytics keys we're injecting (GA4 ID, PostHog project key, etc.) are supposed to ship to the browser; they are public identifiers, not credentials. "Secret" in this context means "centrally managed config," not "must stay confidential." Genuine credentials (e.g., write-access API tokens) never go in PUBLIC_* and never reach the static output — those stay in workflow-only env (no PUBLIC_ prefix) and are used only by CI steps.
Related:
atomic-packages-lazy— analytics stays inline; secrets get injected at build time into those inline snippets