This guide covers deploying the docs-v2 site to staging and production environments, as well as LLM markdown generation.
- Staging Deployment
- Production Deployment
- LLM Markdown Generation
- Testing and Validation
- Troubleshooting
Staging deployments are manual and run locally with your AWS credentials.
-
AWS Credentials - Configure AWS CLI with appropriate permissions:
aws configure
-
s3deploy - Install the s3deploy binary:
./deploy/ci-install-s3deploy.sh
-
Environment Variables - Set required variables:
export STAGING_BUCKET="test2.docs.influxdata.com" export AWS_REGION="us-east-1" export STAGING_CF_DISTRIBUTION_ID="E1XXXXXXXXXX" # Optional
Use the staging deployment script:
yarn deploy:stagingOr run the script directly:
./scripts/deploy-staging.sh- Builds Hugo site with staging configuration (
config/staging/hugo.yml) - Generates LLM-friendly Markdown (
yarn build:md) - Uploads to S3 using s3deploy
- Invalidates CloudFront cache (if
STAGING_CF_DISTRIBUTION_IDis set)
Skip specific steps for faster iteration:
# Skip Hugo build (use existing public/)
export SKIP_BUILD=true
# Skip markdown generation
export SKIP_MARKDOWN=true
# Build only (no S3 upload)
export SKIP_DEPLOY=trueSKIP_DEPLOY=true ./scripts/deploy-staging.shProduction deployments are automatic via CircleCI when merging to master.
-
Build Job (
.circleci/config.yml):- Installs dependencies
- Builds Hugo site with production config
- Generates LLM-friendly Markdown (
yarn build:md) - Persists workspace for deploy job
-
Deploy Job:
- Attaches workspace
- Uploads to S3 using s3deploy
- Invalidates CloudFront cache
- Posts success notification to Slack
Production deployment requires the following environment variables set in CircleCI:
BUCKET- Production S3 bucket nameREGION- AWS regionCF_DISTRIBUTION_ID- CloudFront distribution IDSLACK_WEBHOOK_URL- Slack notification webhook
git push origin masterCircleCI will automatically build and deploy.
Both staging and production deployments generate LLM-friendly Markdown files at build time.
The build generates two types of markdown files in public/:
-
Single-page markdown (
index.md)- Individual page content with frontmatter
- Contains: title, description, URL, product, version, token estimate
-
Section bundles (
index.section.md)- Aggregated section with all child pages
- Includes child page list in frontmatter
- Optimized for LLM context windows
# Generate all markdown
yarn build:md
# Generate for specific path
node scripts/build-llm-markdown.js --path influxdb3/core/get-started
# Limit number of files (for testing)
node scripts/build-llm-markdown.js --limit 100Edit scripts/build-llm-markdown.js to adjust:
// Skip files smaller than this (Hugo alias redirects)
const MIN_HTML_SIZE_BYTES = 1024;
// Token estimation ratio
const CHARS_PER_TOKEN = 4;
// Concurrency (workers)
const CONCURRENCY = process.env.CI ? 10 : 20;- Speed: ~105 seconds for 5,000 pages + 500 sections
- Memory: ~300MB peak (safe for 2GB CircleCI)
- Rate: ~23 files/second with memory-bounded parallelism
If making changes that affect yarn build commands or .circleci/config.yml:
- Read the surrounding context in the CI file
- Notice flags, such as
--destination workspace/publicon the Hugo build - Ask: "Does the build script need to know about environment details--for example, do paths differ between production and staging?"
"This script will run in CI. Let me read the CI configuration to understand the build environment and directory structure before finalizing the implementation."
| Strategy | Implementation | Effort |
|---|---|---|
| Read CI config before implementing | Process/habit change | Low |
| Test on feature branch first | Push and watch CI before merging | Low |
| Add CI validation step | Add file count check in config.yml | Low |
Test markdown generation locally before deploying:
# Prerequisites
yarn install
yarn build:ts
npx hugo --quiet
# Generate markdown for testing
yarn build:md
# Generate markdown for specific path
node scripts/build-llm-markdown.js --path influxdb3/core/get-started --limit 10
# Run validation tests
node cypress/support/run-e2e-specs.js \
--spec "cypress/e2e/content/markdown-content-validation.cy.js"The Cypress tests validate:
- ✅ No raw Hugo shortcodes (
{{< >}}or{{% %}}) - ✅ No HTML comments
- ✅ Proper YAML frontmatter with required fields
- ✅ UI elements removed (feedback forms, navigation)
- ✅ GitHub-style callouts (Note, Warning, etc.)
- ✅ Properly formatted tables, lists, and code blocks
- ✅ Product context metadata
- ✅ Clean link formatting
See DOCS-TESTING.md for comprehensive testing documentation.
Deploys a full build of docs-v2 pull requests to the staging bucket
(https://test2.docs.influxdata.com) for manual validation, under
pr-preview/pr-{number}/.
The preview builds with --environment production, the same Hugo
environment production uses, so it faithfully represents what production
will serve: fingerprinted/SRI JavaScript, minification, real GA4/GTM/Marketo
tracking, and the same __tests__/test_only content exclusions. This
matters for validating site-wide changes (analytics, layout, JS) against a
close approximation of production, not a different build configuration.
CI checks that need __tests__ fixtures or test_only content (Cypress,
render checks) build their own copy of the site in their own job with the
testing/development environment -- they never consume this deployed preview,
so excluding those fixtures from the preview build doesn't affect them.
| File | Purpose |
|---|---|
.github/scripts/parse-pr-urls.js |
Extract docs URLs from PR description |
.github/scripts/detect-preview-pages.js |
Generate deep links for the sticky PR comment |
.github/scripts/preview-comment.js |
Manage sticky PR comments |
.github/workflows/pr-preview.yml |
Main preview workflow |
.github/workflows/cleanup-stale-previews.yml |
Weekly orphan cleanup |
- Full-site deployment - Every push deploys the complete built site, not a hand-picked subset. Previously, previews deployed only pages selected by a change-detection script; that pruning step was the root cause of a recurring bug class (missing pages, broken shared-content links, blank pages when a page's dependencies weren't copied). Deploying the full build removes that failure mode structurally.
- Production-parity build -
hugo-environment: production(not a dedicatedpr-previewenvironment), so the preview matches what ships. - S3 prefix, not GitHub Pages - Deploys to the existing staging bucket
under
pr-preview/pr-{number}/, not thegh-pagesbranch. A full build is too large to deploy togh-pagesrepeatedly -- the branch retains every push's payload in git history forever, since cleanup only removes the working tree, not history. S3 prefixes are deleted for real. - Isolated from manual staging deploys - Manual staging deploys
(
scripts/deploy-staging.sh) go to the bucket root and pass-ignore='^pr-preview/'so they can never delete a live preview. PR preview deploys are scoped to their ownpr-preview/pr-{number}/prefix (both by the-pathflag and by IAM policy), so they can never touch the bucket root or another PR's preview. - Security hardening - XSS protection, path traversal prevention, input validation in the PR-description URL parser.
- Comment deep links, not a deploy gate -
detect-preview-pages.jsstill maps changed content files and PR-description URLs to a page list, but that list only becomes clickable links in the sticky comment. It no longer decides what gets deployed -- the whole site deploys regardless, so there's no "needs author input" state for layout-only changes.
The preview and cleanup workflows authenticate via GitHub OIDC (no long-lived AWS keys in Actions). This requires, before the workflow can run:
- A GitHub OIDC provider trusted by the AWS account (if not already present for this org).
- An IAM role assumable by this repo's
pull_requestandrefs/heads/masterworkflows, scoped to thepr-preview/*object prefix in the staging bucket (s3:GetObject/PutObject/DeleteObject,s3:ListBucketconditioned on that prefix,s3:GetBucketLocation, andcloudfront:CreateInvalidationon the staging distribution only). - Repo secrets/variables:
AWS_PR_PREVIEW_ROLE_ARN,STAGING_BUCKET,STAGING_CF_DISTRIBUTION_ID,AWS_REGION. - The updated
deploy/edge.js(see its top-of-file comments) deployed to the staging distribution's Lambda@Edge association -- this fixes two bugs that would otherwise break previews: a trailing-slash redirect hardcoded todocs.influxdata.com, and unanchored archive/version redirects that would hijack/pr-preview/pr-{number}/...subpaths. - Optionally, an S3 lifecycle rule on the
pr-preview/prefix (e.g. expire after 60 days) as a backstop if the close/cleanup workflows ever fail to remove a preview.
A few things production generates that the preview build still skips, same
as before this change: AI discovery artifacts, llms-full.txt, and the Rust
markdown converter (build-rust: false by default). The preview's HTML also
advertises Markdown-twin alternates that 404, since .md twin generation
isn't run for previews. These don't affect the fidelity that matters most
(rendered HTML, JS, analytics), but are worth knowing about when comparing a
preview pixel-for-pixel against production.
Install the s3deploy binary:
./deploy/ci-install-s3deploy.shVerify installation:
s3deploy -versionCheck required variables are set:
echo $STAGING_BUCKET
echo $AWS_REGIONSet them if missing:
export STAGING_BUCKET="test2.docs.influxdata.com"
export AWS_REGION="us-east-1"Ensure your AWS credentials have the required permissions:
s3:PutObject- Upload files to S3s3:DeleteObject- Delete old files from S3cloudfront:CreateInvalidation- Invalidate cache
Check your AWS profile:
aws sts get-caller-identityCheck for:
- Missing dependencies (
yarn install) - TypeScript compilation errors (
yarn build:ts) - Invalid Hugo configuration
Build Hugo separately to isolate the issue:
yarn hugo --environment stagingCheck for:
- Hugo build completed successfully
- TypeScript compiled (
yarn build:ts) - Sufficient memory available
Test markdown generation separately:
yarn build:md --limit 10If you see stale content after deployment:
- Check
STAGING_CF_DISTRIBUTION_IDis set correctly - Verify AWS credentials have
cloudfront:CreateInvalidationpermission - Manual invalidation:
aws cloudfront create-invalidation \ --distribution-id E1XXXXXXXXXX \ --paths "/*"
For large deployments:
-
Skip markdown generation if unchanged:
SKIP_MARKDOWN=true ./scripts/deploy-staging.sh
-
Use s3deploy's incremental upload:
- s3deploy only uploads changed files
- First deploy is slower, subsequent deploys are faster
-
Check network speed:
- Large uploads require good bandwidth
- Consider deploying from an AWS region closer to the S3 bucket
- Run tests locally (
yarn lint) - Build Hugo successfully (
yarn hugo --environment staging) - Generate markdown successfully (
yarn build:md) - Set staging environment variables
- Have AWS credentials configured
- Test on staging first
- Verify LLM markdown quality
- Check for broken links (
yarn test:links) - Run code block tests (
yarn test:codeblocks:all) - Review CircleCI configuration changes
- Ensure all tests pass