Deploying to the Shared Docs Site¶
How to publish documentation from your repo to docs.saif.com. No per-repo Terraform needed — cloud-foundations owns the infrastructure.
File Structure¶
Add the following to your repo:
.azdo/
azure-docs.yml # deploy pipeline (manual trigger)
azure-docs-pr.yml # PR validation pipeline (no deploy)
vars/
docs.yml # docs config variables
docs/
requirements.txt # Python dependencies for MkDocs
mkdocs.yml # your MkDocs config
SAIF Brand Styling¶
All docs sites share a common SAIF-branded stylesheet hosted by cloud-foundations at docs.saif.com/assets/stylesheets/saif-docs-theme.css. Add it to your mkdocs.yml:
This provides SAIF Evergreen/Pacific branding with light and dark mode support. The stylesheet is maintained in cloud-foundations at docs-root/assets/stylesheets/saif-docs-theme.css, which is a separate MkDocs site (mkdocs.root.yml) that publishes to the $web container root so the URL above stays at the root of the domain. Pages render unstyled if offline during local mkdocs serve.
vars/docs.yml¶
variables:
- name: projectId
value: 'your-repo-name'
- name: documentationSourceDirectory
value: '../../'
- name: outputDirectory
value: 'staticsite'
- name: siteType
value: 'mkdocs'
- name: containerFolderName
value: 'your-folder-name' # sets your URL path: docs.saif.com/{folder}/
azure-docs.yml¶
name: $(MajorVersion).$(MinorVersion).$(PatchVersion)
trigger: none
pr: none
pool:
vmImage: 'ubuntu-latest'
variables:
- group: TerraformCloud
- template: templates/versioning.yml
- template: vars/docs.yml
- template: vars/vars.yml
- template: saif-vars.yml@templates
resources:
repositories:
- repository: templates
type: git
name: SAIF/pipeline-templates
ref: refs/heads/releases/v3
extends:
template: azure-docs-v2.yml@templates
parameters:
projectId: ${{ variables.projectId }}
documentationSourceDirectory: ${{ variables.documentationSourceDirectory }}
outputDirectory: ${{ variables.outputDirectory }}
siteType: ${{ variables.siteType }}
containerFolderName: ${{ variables.containerFolderName }}
# deployToPlatformDev: true # uncomment to also deploy to platformdev
azure-docs-v2.yml is an extends template (it defines the pipeline's outer structure), not
an includes template, so it belongs at the pipeline's own root under extends: — not nested
under stages: - template:. That distinction matters beyond style: if this connection's
service endpoint ever gets a CheckRequiredTemplate check (see
Orchestrator Pipelines and Required Template Checks),
only a root extends: satisfies it. A stages: - template: include of the same file compiles
to the same pipeline but is invisible to that check. The check also matches on repository ref,
not just template path — pin resources.repositories.templates.ref to the same
refs/heads/releases/v3 the check's ciRequiredTemplates entry expects (see
infra/bootstrap/Pulumi.bootstrap.yaml), not refs/heads/main.
azure-docs-pr.yml¶
name: $(MajorVersion).$(MinorVersion).$(PatchVersion)$(PreReleaseVersionSeparator)$(PreReleaseVersion)$(RevisionSeparator)$(Revision)
trigger: none
pr:
branches:
include:
- main
paths:
include:
- .azdo/azure-docs-pr.yml
- .azdo/vars/docs.yml
- docs/*
- mkdocs.yml
pool:
vmImage: 'ubuntu-latest'
variables:
- group: TerraformCloud
- template: templates/versioning.yml
- template: vars/docs.yml
- template: vars/vars.yml
- template: saif-vars.yml@templates
resources:
repositories:
- repository: templates
type: git
name: SAIF/pipeline-templates
ref: refs/heads/releases/v3
extends:
template: azure-docs-pr-v2.yml@templates
parameters:
projectId: ${{ variables.projectId }}
documentationSourceDirectory: ${{ variables.documentationSourceDirectory }}
outputDirectory: ${{ variables.outputDirectory }}
siteType: ${{ variables.siteType }}
containerFolderName: ${{ variables.containerFolderName }}
Same root extends: shape as azure-docs.yml above, and the same refs/heads/releases/v3
pin — azure-docs-pr-v2.yml is allow-listed in the same ciRequiredTemplates entry set, so
copying this example with stages: - template: or the main ref would fail the check the
same way.
Then register both pipeline files in Azure DevOps. No additional service connections or permissions are required.
Frontmatter Contract for the Shared Search Index¶
Pages published to docs.saif.com are also indexed into the shared saif-docs
Azure AI Search index. A few frontmatter conventions affect how your app's pages
rank and de-duplicate in that index. See
the search index reference for the
full field contract.
Set a description in page frontmatter:
MkDocs Material reads page.meta.description for the <meta name="description">
tag, falling back to config.site_description when a page has no frontmatter
description. That fallback means every page in your app without its own
description shares one boilerplate string in the index. The index's scoring
profile weights description content, so a shared boilerplate description is a
real ranking downside, not a cosmetic gap — set per-page descriptions rather
than relying on the fallback.
Tags/keywords are opt-in, and are not a filterable tag list:
MkDocs Material does not emit <meta name="keywords"> out of the box (it only
emits <meta name="description">). Until your app adds a theme override
partial that emits <meta name="keywords" content="..."> from frontmatter
tags, the index's tags field stays null for your pages — harmlessly. This is
something you can opt into; the platform doesn't provide it by default yet.
If you do opt in, understand what you get. tags is a single string, not a
list, so the whole comma-joined keyword string lands in one field. It
contributes to relevance as free text, but it cannot be filtered per tag. Don't
build a tag-filtering feature on it. See
the search index reference
for why it stays that way.
Publish under your containerFolderName subfolder, at least two levels deep:
Publish to $web/{containerFolderName}/..., not to the container root. The
index derives its app and repo fields from the path segment immediately
after $web, and its section field from the segment after that, so
publishing at the root breaks both derivations. cloud-foundations follows the
same rule: its own docs publish under cloud-foundations/, and only the
landing page, the 404 and the shared assets/ folder sit at the container
root.
Depth matters for section. A page at $web/{app}/guides/setup.html gets
section = 'guides'. A page published directly at $web/{app}/index.html gets
section = 'index.html', because there is no folder in that position. Pages at
the container root break it outright — they have no segment in that position at
all, and the indexer drops those documents rather than indexing them with an
empty section.
Use section to let consumers filter out a class of your content:
section is filterable and facetable, so a consumer can exclude a whole
category of pages with a request-level $filter:
This is the supported way to keep low-value-for-search content (release notes, changelogs, generated API dumps) out of an agent's results. Group that content under its own folder so the filter has something to target. A filter works in every query mode, including agentic retrieval, where the index's scoring profile has no effect at all.
Expect both HTML and Markdown twins in search results:
If your app also publishes raw Markdown twins alongside HTML (some apps do
this via a copy_markdown.py-style build step), both the .html and .md
documents for the same page are indexed. Consumers that want exactly one
document per page must apply the $filter:
Do not use a bare format eq '.html' — documents the indexer hasn't yet
reprocessed have format = null and would be wrongly excluded.
Troubleshooting¶
Build succeeds but content doesn't appear at docs.saif.com/{folder}/
- Confirm
containerFolderNamein your vars file matches exactly what you expect in the URL - Check that the pipeline ran against the correct environment (the storage account is per-environment)
- Verify the Front Door cache isn't serving stale content — it may take a few minutes to propagate
mkdocs build fails with a missing plugin or extension
- Add the dependency to
docs/requirements.txtin your repo — the build activity installs from this file automatically