Documentation site

The public documentation lives at https://docs.stream-pulse.cloud, alongside https://stream-pulse.cloud. Jekyll generates static HTML from docs/; GitHub Pages is not used. The container contains only generated public files, not the private repository, Ruby, build dependencies or design plans.

Theme and content

The local theme in docs/_layouts/ and docs/assets/ matches the website’s navy background, cyan links, blue buttons, Stream Pulse wordmark and rounded surfaces. It includes a responsive category sidebar, breadcrumbs, keyboard navigation and client-side search. Browsing works without JavaScript.

Each page needs front matter with title and nav_order. Category indexes use has_children: true; children set parent to the category’s title. Use relative .md links between pages. Pages containing template examples should set render_with_liquid: false so Jekyll preserves {{data}} literally.

docs/plans/ is excluded from both the build context and the generated site. Do not link public guides to excluded plans. Existing synthetic screenshots under docs/assets/images/website/ are shared with the website and illustrated guides. Never add screenshots containing credentials or real account details.

Build and preview

Docker can build the site without installing Ruby locally:

task docs:docker IMAGE=stream-pulse-docs:local
# Equivalent commands, from the repository root:
docker build --target html --output type=local,dest=_site -f docs/Dockerfile .
docker build -f docs/Dockerfile -t stream-pulse-docs:local .
docker run --rm -p 127.0.0.1:5181:8080 stream-pulse-docs:local

Open http://127.0.0.1:5181. task docs:build only generates _site/. The first Docker stage uses Ruby 3.3 and the committed Gemfile.lock; the runtime stage packages _site/ in unprivileged nginx on port 8080. It exposes /healthz, serves pretty directory URLs, returns 404 for missing pages and sets security headers without allowing inline scripts or styles.

To verify the generated site:

$env:DOCS_SITE_DIR = '_site'
$env:DOCS_BASE_URL = 'http://127.0.0.1:5181'
pnpm exec vitest run tests/documentation.test.ts
Remove-Item Env:DOCS_SITE_DIR
Remove-Item Env:DOCS_BASE_URL

For live editing, use bundle install and bundle exec jekyll serve inside docs/ with Ruby 3.3. Update locked dependencies using bundle lock and commit docs/Gemfile.lock. Liquid templates and the front-matter-bearing search JSON are excluded from Prettier because its standard parsers do not support them.

Publishing

The Documentation image workflow runs for docs-related pull requests, pushes to main, and manual dispatches:

  1. Build the html Docker stage and export Jekyll’s generated files.
  2. Test every generated page’s local links/assets, search data, branding, exclusion of internal files and the running static container.
  3. Upload the tested HTML as stream-pulse-docs-html.
  4. Pass that artifact to the shared container workflow, which builds only the runtime stage and publishes ghcr.io/<owner>/stream-pulse-docs.

Pull requests never push images. Main publishes latest and a commit SHA tag; other manual branches publish a SHA tag only. The image includes BuildKit provenance and an SBOM, like the website and gateway images.

Deploy alongside the website

publish/docs.docker-compose.yml joins the same external proxy-tier network as publish/website.docker-compose.yml:

docker compose -f publish/docs.docker-compose.yml pull
docker compose -f publish/docs.docker-compose.yml up -d

Configure the existing reverse proxy to send docs.stream-pulse.cloud to stream-pulse-docs:8080, and stream-pulse.cloud to stream-pulse-website:8080. Set DNS for both hostnames and terminate HTTPS at the proxy. The compose file intentionally exposes no public host port. No automatic production restart or DNS change is performed by the workflow.

GHCR packages created from private repositories may initially be private. Either make the docs package public if intended, or authenticate the host with a token allowed to read it (read:packages). Never make the repository public or store a registry token in the compose file.