skip to content
Skill Issue Dev | Dax the Dev
search
← all docs

Production env setup checklist

Every env var the blog reads, what feature it gates, what to do if it's missing, and where to set it in Cloudflare Pages.

shipped Implemented and deployed.

by Dax the Dev

This is the doc that should have existed before now. Each section gates one feature; setting them turns the feature on and (in most cases) the absence is not fatal — features just no-op gracefully.

The reference values live in .env.example at the repo root. Copy to .env.local for dev, or set in Cloudflare Pages → Settings → Environment variables for prod.

Naming convention

PUBLIC_* — exposed to the client bundle. Safe to read in browser code. Never put secrets here.

Everything else — build-time and server-only. Fine for keys.

1. Comments (Giscus)

VariableRequired?Notes
PUBLIC_COMMENTS_ENABLEDYes (set true to turn on)Master switch
PUBLIC_GISCUS_REPOYes if enablede.g. Dax911/DaxBlogv2
PUBLIC_GISCUS_REPO_IDYes if enabledfrom giscus.app
PUBLIC_GISCUS_CATEGORYOptionaldefault General
PUBLIC_GISCUS_CATEGORY_IDYes if enabledfrom giscus.app

Setup steps:

  1. Enable Discussions on the GitHub repo.
  2. Visit giscus.app and walk through the wizard.
  3. Copy the four IDs from the generated <script> tag into the env vars above.

2. Post signing (Ed25519 attestations)

VariableRequired?Notes
POST_SIGNING_KEYFor prod onlyBase64 PKCS#8 Ed25519 private key

Setup:

bun run sign:keygen        # writes a fresh keypair locally
# Commit the .pem (public key) at src/data/post-public-key.pem
# Set POST_SIGNING_KEY in CF Pages env (the base64 PKCS#8 of the private)

If unset, the prebuild script falls back to an ephemeral keypair — signatures still render but won’t verify against the public PEM. Loud warning in the build log.

3. ActivityPub federation

VariableRequired?Notes
AP_PRIVATE_KEYFor prod onlyBase64 PKCS#8 Ed25519 private key

Setup: Generated by the same script:

bun run sign:keygen activitypub

Without this, the /actor endpoint serves a placeholder pubkey and remote Mastodon instances can’t follow.

4. Audio TTS (Cloudflare Workers AI + R2)

VariableRequired?Notes
CF_ACCOUNT_IDFor audioCloudflare account ID
CF_API_TOKENFor audioToken with Workers AI:Edit + R2:Edit
R2_BUCKET_NAMEFor audioe.g. skill-issue-audio
AUDIO_PUBLIC_URLOptionalDefault https://audio.skill-issue.dev
AUDIO_MODELOptionalDefault @cf/cloudflare/aura-tts
AUDIO_VOICEOptionalDefault default

Setup:

  1. R2 bucket. Create skill-issue-audio in CF dashboard. Bind audio.skill-issue.dev as a public custom domain.
  2. CORS. Set Access-Control-Allow-Origin: https://blog.skill-issue.dev.
  3. API token. Create with the two scopes above. Production scope only — preview deploys keep no-op’ing.

Without these, the prebuild’s render-audio.mjs no-ops with a friendly log line and posts render the ”[ audio coming soon ]” badge instead of an <audio> element.

5. AI Q&A (/ask)

Variable / bindingRequired?Notes
PUBLIC_AI_ASK_ENABLEDOptionalDefault true
Workers AI binding AIYes for prodPages → Functions → Bindings

Setup: In Pages dashboard → Functions → Bindings, add a Workers AI binding with variable name AI. The endpoint reads env.AI.run("@cf/meta/llama-3.3-70b-fp8-fast", ...).

Without the binding, /ask returns 503 with a clear error message.

6. Rate limiting

Optional. Create a KV namespace named RATE_LIMITS and bind it in Pages Functions. Without it, /ask and /verify-signature are unrate-limited (fine for low-traffic launch; needed when traffic picks up).

7. Resume / Papers PDF

VariableRequired?Notes
PUBLIC_PDF_RESUME_ENABLEDOptionalDefault true
RESUME_PDF_URLOptionalDefault /resume.pdf
PUBLIC_PAPERS_PDF_ENABLEDOptionalDefault false (skips PDF gen)
PUPPETEER_EXECUTABLE_PATHIf papers PDF enabledpath to a Chrome/Chromium binary

The papers PDF flag is off by default because puppeteer adds ~250MB to the build image. Turn on only when you actually need the artifact.

8. GitHub contribution graph

VariableRequired?Notes
GITHUB_TOKENOptionalA read-only PAT (no scopes) bumps API limits

The graph data is fetched at build time by scripts/fetch-github-graph.mjs and committed to src/data/github-graph.json. Without a token the script uses an anonymous GraphQL request — works but is rate-limited to 60/hour per IP.

9. Cloudflare Pages — non-env settings

These aren’t env vars but are gotchas worth pinning:

  • Build command: bun run build
  • Build output dir: dist
  • Node version: the .nvmrc says 22.11.0. CF Pages uses 22.22.0 by default which works.
  • Compatibility flags: nodejs_compat (Functions setting). Without it, anything using node:* modules at runtime (e.g. ActivityPub key-loading) errors.
  • Rocket Loader: disable. It rewrites inline scripts and breaks ThemeInit + service-worker registration. Cloudflare → Speed → Optimization → Rocket Loader → Off.

Quick verify

After setting env vars, force-rebuild on CF Pages and check the build log for these lines:

[sign-posts] signed N post(s), skipped 1, wrote /opt/buildhome/repo/src/data/post-signatures.json
[render-audio] CF_ACCOUNT_ID / CF_API_TOKEN / R2_BUCKET_NAME not set — skipping
                ↑ this should disappear once audio env is set

The [sign-posts] WARN: POST_SIGNING_KEY not set line should disappear once the signing key is in place. If it persists, your env var name has a typo.

What hasn’t been documented yet

  • Plausible analytics: configured via DOM data attributes in BaseHead.astro, not env vars. Fine, but worth a follow-up post.
  • Telegram bot integration (kinkbook/pike_pupday side projects): different repo, different env, beyond this doc’s scope.
  • Apple notarization (APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID): for vanta-desktop release builds only, not the blog.