Skip to content

Deploy Static Docs

Deploy Static Docs #43

name: Deploy Static Docs
# Renders the published docs site to static HTML and uploads it to Cloudflare
# Pages using the public fernenterprise/docs-static-build container.
#
# Runs AFTER "Publish Docs" completes successfully, so the ledger manifest + CAS
# blobs the container reads from S3 are already up to date. Also runnable
# manually via workflow_dispatch.
#
# The container takes three logical inputs (see the fern-platform astro
# package README): the docs URL, an S3 key to READ the ledger blobs, and a CF
# Pages key to UPLOAD the built site. All are provided as GitHub secrets.
# workflow_run is required so this runs after "Publish Docs" updates the ledger.
# It's safe here: the job checks out no (untrusted) code, is granted no token
# permissions (permissions: {}), and only runs a pinned public container with
# read-only S3 + Cloudflare deploy credentials.
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: ["Publish Docs"]
types:
- completed
workflow_dispatch:
inputs:
image_tag:
description: "fernenterprise/docs-static-build tag to run"
required: false
default: "latest"
type: string
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions: {}
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 60
# For workflow_run: only proceed when the upstream "Publish Docs" run
# succeeded (a failed/cancelled publish leaves the ledger unchanged, so
# there's nothing new to build). workflow_dispatch runs always proceed.
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- name: Build static docs and deploy to Cloudflare Pages
env:
# 1. Docs URL — the site to build.
NEXT_PUBLIC_DOCS_DOMAIN: fern.docs.buildwithfern.com/learn
# This is the real production deploy of buildwithfern.com/learn. Off
# Vercel there is no VERCEL_ENV, so without this the build treats itself
# as a preview and bakes `noindex, nofollow` + `robots.txt: Disallow: /`
# into every page.
FERN_DOCS_DEPLOYMENT_ENV: production
# 2. S3 key — read the published ledger manifest + CAS blobs.
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: us-east-1
DOCS_DEFINITION_S3_BUCKET_NAME: fdr-prod-docs-definitions-public
CONTENT_ADDRESSABLE_FILES_S3_BUCKET_NAME: fdr-prod-content-addressable-storage
# 3. CF Pages key — upload the built dist/ to Cloudflare Pages.
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_PAGES_PROJECT: ${{ secrets.CF_PAGES_PROJECT }}
CF_PAGES_BRANCH: ${{ secrets.CF_PAGES_BRANCH }}
# Optional: used only to resolve Ask AI enablement at build time.
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
# Vercel Edge Config connection string. Read at build time to resolve
# per-domain edge flags (e.g. `isWhitelabeled`, which hides the
# "Built with Fern" footer badge). If empty, the edge-config read
# returns undefined and getEdgeFlags falls back to all-false defaults,
# leaking the badge onto every page — see the guard in the run step.
EDGE_CONFIG: ${{ secrets.EDGE_CONFIG }}
IMAGE_TAG: ${{ github.event.inputs.image_tag || 'latest' }}
run: |
IMAGE="fernenterprise/docs-static-build:${IMAGE_TAG}"
echo "Running $IMAGE for $NEXT_PUBLIC_DOCS_DOMAIN"
# Hard guard: this workflow only ever builds one known-whitelabeled
# site (fern.docs.buildwithfern.com), so a missing EDGE_CONFIG means the
# edge flags default to false and the "Built with Fern" badge leaks —
# that's always wrong here. Fail the deploy rather than ship it green.
if [ -z "${EDGE_CONFIG:-}" ]; then
echo "::error title=EDGE_CONFIG missing::EDGE_CONFIG is empty — edge flags (whitelabel, etc.) would fall back to defaults and leak the 'Built with Fern' badge. Set the EDGE_CONFIG repo secret."
exit 1
fi
docker pull "$IMAGE"
# Forward each secret by name (values come from this step's env), so no
# secret is interpolated into the command line.
docker run --rm \
-e NEXT_PUBLIC_DOCS_DOMAIN \
-e FERN_DOCS_DEPLOYMENT_ENV \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_REGION \
-e DOCS_DEFINITION_S3_BUCKET_NAME \
-e CONTENT_ADDRESSABLE_FILES_S3_BUCKET_NAME \
-e CLOUDFLARE_API_TOKEN \
-e CLOUDFLARE_ACCOUNT_ID \
-e CF_PAGES_PROJECT \
-e CF_PAGES_BRANCH \
-e FERN_TOKEN \
-e EDGE_CONFIG \
"$IMAGE"