Learning Track

Documentation Team Lead

Set up scalable documentation workflows with validation and CI/CD.

1 min read 28 words
Edit this page

Was this page helpful?

1
2
3
4
5
6
7
8
Step 1 of 8

Scale your documentation with schemas, validation, remote sources, and CI/CD pipelines. Perfect for teams that need content governance.

Tip

Duration: ~75 min | Prerequisite: Experience managing documentation

1

Content Collections

Validate frontmatter with typed schemas

Content Collections

Define typed schemas for your content to ensure consistency and catch errors early.

Do I Need This?

No. Collections are optional. Your site works fine without them.

Use collections when:

  • You want typos caught at build time, not in production
  • Multiple people edit content and need guardrails
  • You want consistent frontmatter across content types

Quick Setup

Create acollections.pyfile at your project root. Edit it to uncomment what you need:

PYTHON
from bengal.collections import define_collection, BlogPost, DocPage

collections = {
    "blog": define_collection(schema=BlogPost, directory="blog"),
    "docs": define_collection(schema=DocPage, directory="docs"),
}

Done. Build as normal—validation happens automatically.

Built-in Schemas

Bengal provides schemas for common content types:

Schema Alias Required Fields Optional Fields
BlogPost Post title, date author, tags, draft, description, image, excerpt
DocPage Doc title weight, category, tags, toc, deprecated, description, since
APIReference API title, endpoint method, version, auth_required, rate_limit, deprecated, description
Tutorial title difficulty, duration, prerequisites, series, tags, order
Changelog title, date version, breaking, summary, draft

Import any of these:

PYTHON
from bengal.collections import BlogPost, DocPage, APIReference, Tutorial, Changelog
# Or use short aliases:
from bengal.collections import Post, Doc, API

Custom Schemas

Define your own using Python dataclasses:

PYTHON
from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class ProjectPage:
    title: str
    status: str  # "active", "completed", "archived"
    started: datetime
    tech_stack: list[str] = field(default_factory=list)
    github_url: str | None = None

collections = {
    "projects": define_collection(
        schema=ProjectPage,
        directory="projects",
    ),
}

Validation Modes

By default, validation warns but doesn't fail builds:

TREE-SITTER-QUERY
content/blog/my-post.md
  └─ date: Required field 'date' is missing

Strict Mode

To fail builds on validation errors, add tobengal.toml:

TOML
[build]
strict_collections = true

Lenient Mode (Extra Fields)

To allow frontmatter fields not defined in your schema:

PYTHON
define_collection(
    schema=BlogPost,
    directory="blog",
    strict=False,       # Don't reject unknown fields
    allow_extra=True,   # Store extra fields in _extra dict
)

With strict=False, unknown fields are silently ignored. Add allow_extra=True to preserve them in a _extraattribute on the validated instance.

CLI Commands

BASH
# List defined collections and their schemas
bengal content collections

# Validate content against schemas without building
bengal content schemas

# Validate specific collection
bengal content schemas --collection blog

Advanced Options

Custom File Pattern

By default, collections match all markdown files (**/*.md). To match specific files:

PYTHON
define_collection(
    schema=BlogPost,
    directory="blog",
    glob="*.md",  # Only top-level, not subdirectories
)

Migration Tips

Existing site with inconsistent frontmatter?

  1. Start withstrict=Falseto allow extra fields
  2. Runbengal content schemasto find issues
  3. Fix content or adjust schema
  4. Enablestrict=Truewhen ready

Transform legacy field names:

PYTHON
def migrate_legacy(data: dict) -> dict:
    if "post_title" in data:
        data["title"] = data.pop("post_title")
    return data

collections = {
    "blog": define_collection(
        schema=BlogPost,
        directory="blog",
        transform=migrate_legacy,
    ),
}

Remote Content

Collections work with remote content too. Use a loader instead of a directory:

PYTHON
from bengal.collections import define_collection, DocPage
from bengal.content.sources import github_loader

collections = {
    "api-docs": define_collection(
        schema=DocPage,
        loader=github_loader(repo="myorg/api-docs", path="docs/"),
    ),
}

See Content Sources for GitHub, Notion, REST API loaders.

Seealso

2

Content Sources

Fetch content from external sources

Remote Content Sources

Fetch content from GitHub, Notion, REST APIs, and more.

Do I Need This?

No. By default, Bengal reads content from local files. That works for most sites.

Use remote sources when:

  • Your docs live in multiple GitHub repos
  • Content lives in a CMS (Notion, Contentful, etc.)
  • You want to pull API docs from a separate service
  • You need to aggregate content from different teams

Quick Start

Install the loader you need:

BASH
pip install bengal[github]   # GitHub repositories
pip install bengal[notion]   # Notion databases
pip install bengal[rest]     # REST APIs
pip install bengal[all-sources]  # Everything

Update your collections.py:

PYTHON
from bengal.collections import define_collection, DocPage
from bengal.content.sources import github_loader

collections = {
    # Local content (default)
    "docs": define_collection(
        schema=DocPage,
        directory="content/docs",
    ),

    # Remote content from GitHub
    "api-docs": define_collection(
        schema=DocPage,
        loader=github_loader(
            repo="myorg/api-docs",
            path="docs/",
        ),
    ),
}

Build as normal. Remote content is fetched, cached, and validated like local content.

Available Loaders

GitHub

Fetch markdown from any GitHub repository:

PYTHON
from bengal.content.sources import github_loader

loader = github_loader(
    repo="owner/repo",       # Required: "owner/repo" format
    branch="main",           # Default: "main"
    path="docs/",            # Default: "" (root)
    token=None,              # Default: uses GITHUB_TOKEN env var
    glob="*.md",             # Default: "*.md" (file pattern to match)
)

For private repos, set GITHUB_TOKEN environment variable or pass tokendirectly.

Notion

Fetch pages from a Notion database:

PYTHON
from bengal.content.sources import notion_loader

loader = notion_loader(
    database_id="abc123...",  # Required: database ID from URL
    token=None,               # Default: uses NOTION_TOKEN env var
    property_mapping={        # Map Notion properties to frontmatter
        "title": "Name",
        "date": "Published",
        "tags": "Tags",
    },
)

Setup:

  1. Create integration at notion.so/my-integrations
  2. Share your database with the integration
  3. SetNOTION_TOKENenvironment variable

REST API

Fetch from any JSON API:

PYTHON
from bengal.content.sources import rest_loader

loader = rest_loader(
    url="https://api.example.com/posts",
    headers={"Authorization": "Bearer ${API_TOKEN}"},  # Env vars expanded
    content_field="body",           # JSON path to content
    id_field="id",                  # JSON path to ID
    frontmatter_fields={            # Map API fields to frontmatter
        "title": "title",
        "date": "published_at",
        "tags": "categories",
    },
)

Local (Explicit)

For consistency, you can also use an explicit local loader:

PYTHON
from bengal.content.sources import local_loader

loader = local_loader(
    directory="content/docs",
    glob="**/*.md",
    exclude=["_drafts/*"],
)

Caching

Remote content is cached locally to avoid repeated API calls:

BASH
# List configured sources
bengal content sources

# Force refresh from remote (ignore cache)
bengal content fetch --force

Cache behavior:

  • Default TTL: 1 hour
  • Cache directory:.bengal/content_cache/
  • Automatic invalidation when config changes
  • Falls back to cache if remote unavailable

CLI Commands

BASH
# List configured content sources
bengal content sources

# Fetch/refresh from remote sources
bengal content fetch
bengal content fetch --filter-source api-docs  # Specific source
bengal content fetch --force                   # Ignore cache

Environment Variables

Variable Used By Description
GITHUB_TOKEN GitHub loader Personal access token for private repos
NOTION_TOKEN Notion loader Integration token
Custom REST loader Any${VAR}in headers is expanded

Multi-Repo Documentation

A common pattern for large organizations:

PYTHON
from bengal.collections import define_collection, DocPage
from bengal.content.sources import github_loader, local_loader

collections = {
    # Main docs (local)
    "docs": define_collection(
        schema=DocPage,
        directory="content/docs",
    ),

    # API reference (from API team's repo)
    "api": define_collection(
        schema=DocPage,
        loader=github_loader(repo="myorg/api-service", path="docs/"),
    ),

    # SDK docs (from SDK repo)
    "sdk": define_collection(
        schema=DocPage,
        loader=github_loader(repo="myorg/sdk", path="docs/"),
    ),
}

Custom Loaders

ImplementContentSourcefor any content origin:

PYTHON
from collections.abc import AsyncIterator
from bengal.content.sources import ContentSource, ContentEntry

class MyCustomSource(ContentSource):
    source_type = "my-api"

    async def fetch_all(self) -> AsyncIterator[ContentEntry]:
        items = await self._get_items()
        for item in items:
            yield ContentEntry(
                id=item["id"],
                slug=item["slug"],
                content=item["body"],
                frontmatter={"title": item["title"]},
                source_type=self.source_type,
                source_name=self.name,
            )

    async def fetch_one(self, id: str) -> ContentEntry | None:
        item = await self._get_item(id)
        if not item:
            return None
        return ContentEntry(
            id=item["id"],
            slug=item["slug"],
            content=item["body"],
            frontmatter={"title": item["title"]},
            source_type=self.source_type,
            source_name=self.name,
        )

Zero-Cost Design

If you don't use remote sources:

  • No extra dependencies installed
  • No network calls
  • No import overhead
  • No configuration needed

Remote loaders are lazy-loaded only when you import them.

Other Content Sources

  • Autodoc — Generate API docs from Python, CLI commands, and OpenAPI specs

Seealso

3

Validation

Content validation and health checks

Content Validation

Catch broken links, bad directives, and config drift before they reach production.

Note

Do I need this? Yes when you want automated quality checks in CI or before deploy. Skip if you are still prototyping locally and fixing issues by hand. For the full validate-and-fix workflow, see Validate and Fix. For auto-fix, custom validators, and health configuration, see Validation Reference.

Validation Flow

flowchart LR A[Content] --> B[Validators] B --> C{Issues?} C -->|Yes| D[Report] C -->|No| E[Pass] D --> F{Auto-fixable?} F -->|Yes| G[Auto-fix] F -->|No| H[Manual fix needed]

Quick Start

BASH
# Run all checks
bengal check

# Validate specific files
bengal check --file content/page.md

# Only validate changed files (incremental)
bengal check --changed

# Verbose output (show all checks)
bengal check --verbose

# Show quality suggestions
bengal check --suggestions

# Watch mode (validate on file changes)
bengal check --watch
BASH
# Preview fixes
bengal fix --dry-run

# Apply safe fixes
bengal fix

# Apply all fixes including confirmations
bengal fix --all

# Fix specific validator only
bengal fix --validator Directives

Fixes common issues:

  • Unclosed directive fences
  • Invalid directive options
  • YAML syntax errors
BASH
# Fail build on issues
bengal build --strict

# Validate and exit with error code
bengal check

The --strictflag makes warnings into errors.

Built-in Checks

Check What it validates
links Internal and external links work
assets Asset references exist
config Configuration is valid
navigation Menu structure is correct
rendering Templates render without errors
cross_ref Cross-references are valid
taxonomy Tags and categories are consistent
directives MyST directive syntax is correct
anchors Heading IDs are unique and valid

Custom Validators

Create project-specific rules by extendingBaseValidator:

PYTHON
# validators/custom.py
from bengal.health.base import BaseValidator
from bengal.health.report import CheckResult

class RequireAuthorValidator(BaseValidator):
    """Validator that checks for author field in frontmatter."""

    name = "Author Required"
    description = "Ensures all pages have an author field"

    def validate(self, site, build_context=None):
        results = []
        for page in site.pages:
            if not page.metadata.get("author"):
                results.append(CheckResult.error(
                    f"Missing author in {page.source_path}",
                    recommendation="Add 'author: Your Name' to frontmatter",
                    details=[str(page.source_path)],
                ))
        return results

Tip

CI integration: Addbengal check to your CI pipeline to catch issues before deployment. Use --verbose to see all checks, or --suggestionsfor quality recommendations.

4

Validate and Fix

Run health checks and automatically fix common content issues

Validate and Fix Content

Note

Do I need this? Yes when runningbengal check or bengal fixin your workflow. For auto-fix details, custom validators, and health config, see Validation Reference.

Bengal's health system validates content and can automatically fix many common issues.

Quick Start

BASH
# Run all validators
bengal check

# Preview auto-fixes
bengal fix --dry-run

# Apply safe fixes
bengal fix

Validation Commands

Basic Validation

BASH
# Validate entire site
bengal check

# Validate specific file
bengal check --file content/docs/getting-started.md

# Validate changed files only (git-aware)
bengal check --changed

# Verbose output - show all checks, not just errors
bengal check --verbose

Validate During Build

BASH
# Fail build on validation errors (recommended for CI)
bengal build --strict

# Validate templates before building
bengal build --validate

Check Specific Areas

BASH
# Link checking (internal + external)
bengal inspect links

# Internal links only (fast)
bengal inspect links --internal-only

# External links only
bengal inspect links --external-only

# Exclude specific URL patterns
bengal inspect links --exclude "^/api/preview/"

Available Validators

Bengal includes validators organized by phase:

Core Validators

Validator Checks Common Issues
Links Internal/external links Broken links, moved pages
Directives MyST directive syntax Unclosed fences, invalid options
Configuration Site configuration Invalid YAML, missing required fields
Navigation Page nav (next/prev, breadcrumbs) Broken navigation links
Navigation Menus Menu structure and links Missing menu items, broken links

Content Quality Validators

Validator Checks Common Issues
Anchors Heading IDs,[[#anchor]]refs Duplicate IDs, broken anchor links
Cross-References Internal page references Invalid page references
Taxonomies Tags/categories Orphan terms, inconsistent naming
Connectivity Page link graph Orphan pages, poor connectivity

Build & Output Validators

Validator Checks Common Issues
Rendering HTML output quality Template errors, undefined variables, missing social tags, malformed JSON-LD
Output Generated pages, assets Missing output, structure errors
Asset URLs Asset references in HTML Broken asset paths, fingerprinting mismatches, case issues
Performance Build metrics Slow builds, large pages
URL Collisions Duplicate output paths Multiple pages writing to same URL

Production Validators

Validator Checks Common Issues
Sitemap sitemap.xml validity SEO issues, missing pages
RSS Feed RSS/Atom feed quality Schema compliance, missing fields
Fonts Font downloads, CSS Missing fonts, subsetting issues
Ownership Policy Reserved namespaces Content in system directories

Validation Output

BASH
$ bengal check

🔍 Running health checks...

✅ Config: 3 checks passedNavigation: 5 checks passed
⚠️ Links: 2 warningscontent/docs/old-page.md references moved pagecontent/api/client.md has broken anchor #deprecatedDirectives: 1 errorcontent/tutorials/setup.md:45 - Unclosed code fence

Summary: 1 error, 2 warnings, 8 passed

CI/CD Integration

GitHub Actions

YAML
name: Validate Docs

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.14'

      - name: Install Bengal
        run: pip install bengal

      - name: Validate Content
        run: bengal check --verbose

      - name: Check Links
        run: bengal inspect links --internal-only

      - name: Build (strict mode)
        run: bengal build --strict

Pre-Commit Hook

YAML
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: bengal-validate
        name: Validate Bengal content
        entry: bengal check --changed
        language: system
        pass_filenames: false

Failing on Errors

BASH
# Strict mode - treats warnings as errors
bengal build --strict

# Validate and exit with error code
bengal check && echo "Validation passed" || echo "Validation failed"

Seealso

5

Analysis

Site structure analysis tools

Site Analysis

Analyze your site's structure to improve navigation and discoverability.

Note

Do I need this? Skip if your site has under 50 pages. Read when you want to find orphan pages, improve internal linking, or run connectivity checks in CI.

Analysis Tools

flowchart LR A[Your Site] --> B[Analysis Engine] subgraph Outputs C[Connectivity Report] D[Link Suggestions] E[PageRank] end B --> C B --> D B --> E

Quick Start

BASH
# Unified connectivity report
bengal graph report

# Brief output for CI
bengal graph report --brief

# CI mode with thresholds
bengal graph report --ci --threshold-isolated 5

Shows:

  • Connectivity distribution
  • Isolated/lightly-linked pages
  • Bridge pages
  • Actionable recommendations
BASH
# Find isolated pages
bengal graph orphans

# Find lightly-linked pages
bengal graph orphans --level lightly

# JSON output for processing
bengal graph orphans --format json

# Paths only (for scripting)
bengal graph orphans --format paths

Identifies pages by connectivity level:

  • 🔴 Isolated (score < 0.25)
  • 🟠 Lightly linked (0.25-1.0)
  • 🟡 Adequately linked (1.0-2.0)
  • 🟢 Well-connected (≥ 2.0)
BASH
bengal graph pagerank --top-n 20

Identifies:

  • Most important pages
  • Underlinked valuable content
  • Navigation priorities

Use Cases

Goal Command Output
Get site health overview bengal graph report Connectivity distribution and recommendations
Find isolated pages bengal graph orphans Pages needing attention
Find bridge pages bengal graph bridges Navigation bottlenecks
Identify key content bengal graph pagerank --top-n 20 Pages ranked by importance

Tip

Start withbengal graph report for a unified view of your site structure. Use --cimode in pipelines to fail builds when connectivity thresholds are exceeded.

6

Configuration

Configuring Bengal with bengal.toml

Configuration

Configure builds, environments, themes, and site metadata before you ship.

Note

Do I need this? Yes when tuning production builds, multi-environment setups, or theme selection. Skip on day one —bengal new sitescaffolds sensible defaults.

Configuration Methods

flowchart TB subgraph "Base Configuration (Mutually Exclusive)" A[bengal.toml] B[config/ directory] end C[Environment Overrides] D[CLI Flags] E[Final Config] A -.->|OR| E B -.->|OR| E C --> E D --> E

Bengal loads configuration from either theconfig/ directory (preferred) OR bengal.toml (legacy/simple). If config/ exists, bengal.tomlis ignored.

Overrides apply in order: Base Config → Environment Overrides → CLI Flags.

Quick Start

TOML
# bengal.toml
[site]
title = "My Site"
baseurl = "https://example.com"
language = "en"

[build]
output_dir = "public"

[theme]
name = "default"

Configuration Patterns

Best for small sites:

TOML
# bengal.toml - everything in one place
[site]
title = "My Blog"

[build]
output_dir = "public"

[theme]
name = "default"

Best for larger sites:

TREE-SITTER-QUERY
config/
├── _default/
│   ├── site.yaml
│   ├── build.yaml
│   └── theme.yaml
└── environments/
    ├── production.yaml
    └── staging.yaml

Environment Overrides

Run with different settings per environment:

BASH
bengal build --environment production
YAML
# config/environments/production.yaml
site:
  baseurl: "https://example.com"

build:
  minify_html: true
  strict_mode: true

assets:
  fingerprint: true

Tip

Best practice: Keep development settings inbengal.toml, add production overrides in config/environments/production.yaml.

Build Options Reference

Key[build]configuration options:

Option Type Default Description
output_dir string "public" Directory for generated files
minify_html bool true Minify HTML output
validate_templates bool false Proactive template syntax validation
validate_build bool true Post-build validation checks
validate_links bool true Check for broken internal links
strict_mode bool false Fail build on any error or warning
fast_mode bool false Enable maximum performance optimizations

Note

Incremental builds are automatic. First build is full (creates cache), subsequent builds only rebuild changed content. Use--no-incrementalCLI flag for debugging or CI clean builds.

Asset Options

Configure asset processing in the[assets]section:

Option Type Default Description
minify bool true Minify CSS/JS assets
optimize bool true Optimize images
fingerprint bool true Add content hash to asset URLs
TOML
[assets]
minify = true
optimize = true
fingerprint = true

Template Validation

Enablevalidate_templatesto catch template syntax errors early during builds:

TOML
[build]
validate_templates = true

When enabled, Bengal validates all templates (HTML/XML) in your template directories before rendering. This provides early feedback on syntax errors, even for templates that might not be used by every page.

Enable template validation during development for immediate feedback:

TOML
[build]
validate_templates = true

Combine with strict mode in CI pipelines to fail builds on template errors:

TOML
[build]
validate_templates = true
strict_mode = true

When to enable:

  • During active theme development
  • In CI/CD pipelines
  • When debugging template issues

What it catches:

  • Template syntax errors (unclosed tags, invalid filters) in Kida and Jinja2
  • Unknown filter names
  • Template assertion errors

Note

Template validation adds a small overhead to build time. For large sites, consider enabling it only in development and CI environments.

For Kida templates,bengal check --templates-securityruns Kida's static escape and privacy analysis. It reports trust-boundary warnings such as unexplained | safeuse and sensitive-looking context paths without failing the check unless Kida reports an error-level finding.

bengal check --templates-contextalso uses Kida's dotted-path context contract checker. It can catch missing nested context such aspage.titlewhile treating dynamic customization roots likeparams.* and site.data.*as author-provided data.

Kida Options

Kida-specific options live under[kida] in bengal.toml or kida:in YAML config files.template_aliases maps @alias/prefixes to paths relative to the active template roots:

TOML
[kida]
bytecode_cache = true
static_context = false

[kida.template_aliases]
components = "ui/components"

Templates can then include shared files with {% include "@components/card.html" %}.

Multi-Variant Builds

Build different site variants (OSS vs Enterprise, brand1 vs brand2) from one content tree. Setparams.edition per environment and add editionto page frontmatter to filter content.

Seealso

Multi-Variant Builds — Full guide for OSS/Enterprise and brand variants

7

Deployment

Deploy your Bengal site to production

Deploy Your Site

Ship static HTML from Bengal to GitHub Pages, Netlify, Vercel, or any host that serves files from a directory.

Note

Do I need this? Yes when you are ready to publish beyondbengal serve. Skip until you have content to ship — local preview does not need deployment config. For CI wiring, also see GitHub Actions tutorial.

Bengal generates static HTML, CSS, and JavaScript. Run a production build, upload thepublic/directory, and point your host at it.

The Production Build

When you are ready to ship, run the production build:

BASH
bengal build --environment production --strict --clean-output

Output goes to public/. Use --fastfor CI builds when you do not need verbose timing output.

This command:

  • Loads configuration fromconfig/environments/production.yaml(if it exists)
  • Minifies HTML output (enabled by default)
  • Generates thepublic/directory with your complete site

Common Build Flags

Flag Description Use Case
--environment production Loads production config overrides. Always use for shipping.
--strict Fails the build on template errors. Highly Recommended for CI/CD.
--clean-output Cleans thepublic/directory before building. Recommended to avoid stale files.
--fast Maximum performance (quiet output, full parallelism). Fast CI builds.
--verbose Shows detailed build output (phase timing, stats). Useful for debugging CI failures.

Example full command for CI matches the snippet above — add--fastwhen you want maximum CI throughput with quiet output.

GitHub Pages

Deploy using GitHub Actions. Create.github/workflows/deploy.yml:

YAML
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.14'

      - name: Install Bengal
        run: pip install bengal

      - name: Build Site
        run: bengal build --environment production --strict --clean-output

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: './public'

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Netlify

Create anetlify.tomlin your repository root:

TOML
[build]
  publish = "public"
  command = "bengal build --environment production"

[build.environment]
  PYTHON_VERSION = "3.14"

Vercel

Configure your project:

  1. Build Command:bengal build --environment production
  2. Output Directory:public
  3. Ensure yourrequirements.txt includes bengal.

Automatic Platform Detection

Bengal auto-detects your deployment platform and configuresbaseurlautomatically:

Platform Detection Baseurl Source
GitHub Pages GITHUB_ACTIONS=true Inferred fromGITHUB_REPOSITORY
Netlify NETLIFY=true URL or DEPLOY_PRIME_URL
Vercel VERCEL=true VERCEL_URL

You can override auto-detection with theBENGAL_BASEURLenvironment variable:

BASH
BENGAL_BASEURL="https://custom-domain.com" bengal build --environment production

Pre-Deployment Checklist

Before you merge to main or deploy:

  1. Runbengal config doctor: Checks for common configuration issues.
  2. Runbengal build --strictlocally: Ensures no template errors.
  3. Runbengal check: Runs health checks on your site content.
  4. Checkconfig/environments/production.yaml: Ensure your baseurlis set to your production domain.
YAML
# config/environments/production.yaml
site:
  baseurl: "https://example.com"

Seealso

8

Automate with GitHub Actions

Set up automated builds, testing, and deployments using GitHub Actions

Automate with GitHub Actions

Set up continuous integration and deployment (CI/CD) for your Bengal site.

Note

Do I need this? Yes when automating builds, preview deploys, or CI validation. For a manual deploy checklist, see Deployment first.

Automate builds, run tests, and deploy to production with GitHub Actions.

Prerequisites

  • Bengal installed
  • A Git repository on GitHub
  • A hosting provider account (GitHub Pages, Netlify, Vercel, etc.)
  • Basic knowledge of YAML

Performance Tip

For faster CI builds, use Python 3.14t (free-threading build) instead of 3.14. This enables true parallel processing and can reduce build times by 1.5-2x on multi-core runners. Updatepython-version: '3.14t'in your workflows.

Steps

  1. 1

    Basic Build Workflow

    Create.github/workflows/build.yml:

    YAML
    name: Build Site
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: '3.14'
    
          - name: Install Bengal
            run: pip install bengal
    
          - name: Build site
            run: bengal build --environment production --strict
    
          - name: Upload artifacts
            uses: actions/upload-artifact@v4
            with:
              name: site
              path: public/
              retention-days: 1
    
  2. 2

    Deploy to GitHub Pages

    Automatically deploys to GitHub Pages when you push tomain. Requires GitHub Pages enabled in repository settings.

    Create.github/workflows/deploy.yml:

    YAML
    name: Deploy to Production
    
    on:
      push:
        branches: [main]
    
    # Required permissions for GitHub Pages deployment
    permissions:
      contents: read
      pages: write
      id-token: write
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: '3.14'
              cache: 'pip'
    
          - name: Install Bengal
            run: pip install bengal
    
          - name: Build site
            run: bengal build --environment production --strict --clean-output
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v4
            with:
              path: './public'
    
      deploy:
        environment:
          name: github-pages
          url: ${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v4
    

    Note: Enable GitHub Pages in your repository settings: Settings > Pages > Source: GitHub Actions.

  3. 3

    Preview Deployments

    Builds preview versions for pull requests. Comments on PRs when build succeeds. Artifacts available in workflow run.

    Create.github/workflows/preview.yml:

    YAML
    name: Preview Deployment
    
    on:
      pull_request:
        branches: [main]
    
    jobs:
      preview:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: '3.14'
              cache: 'pip'
    
          - name: Install Bengal
            run: pip install bengal
    
          - name: Build site
            run: bengal build --environment preview
    
          - name: Upload preview artifacts
            uses: actions/upload-artifact@v4
            with:
              name: preview-site
              path: public/
              retention-days: 7
    
          - name: Comment PR with preview
            uses: actions/github-script@v7
            with:
              script: |
                github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: '✅ Preview build successful! Download artifacts from workflow run.'
                })
    
  4. 4

    Add Validation and Testing

    Add health checks to your CI pipeline:

    YAML
    # .github/workflows/test.yml
    name: Test and Validate
    
    on: [push, pull_request]
    
    jobs:
      validate:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: '3.14'
    
          - name: Install Bengal
            run: pip install bengal
    
          - name: Validate configuration
            run: bengal config doctor
    
          - name: Check for broken links
            run: bengal inspect links
    
          - name: Build with strict mode
            run: bengal build --strict --verbose
    
  5. 5

    Caching for Faster Builds

    Cache dependencies and build artifacts to reduce workflow time. Add these steps after Python setup:

    YAML
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.14'
        cache: 'pip'  # Automatically caches pip packages
    
    - name: Cache Bengal build cache
      uses: actions/cache@v4
      with:
        path: .bengal-cache
        key: ${{ runner.os }}-bengal-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-bengal-
    

    Note: Python setup with cache: 'pip'automatically caches pip packages. Only add Bengal cache if you use incremental builds.

  6. 6

    Environment-Specific Builds

    Use different configurations for production and preview builds. Store secrets in GitHub repository settings.

    1. Create environment configs:

    config/environments/production.yaml:

    YAML
    site:
      baseurl: "https://example.com"
    
    params:
      analytics_id: "{{ env.GA_ID }}"
    

    config/environments/preview.yaml:

    YAML
    site:
      baseurl: "https://preview.example.com"
    
    params:
      analytics_id: ""  # Disable analytics in preview
    

    2. Add environment variables to workflow:

    YAML
    env:
      GA_ID: ${{ secrets.GA_ID }}
      API_KEY: ${{ secrets.API_KEY }}
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          # ... other steps ...
          - name: Build site
            run: bengal build --environment production
    

    3. Set secrets in GitHub: Settings > Secrets and variables > Actions > New repository secret

  7. 7

    Multi-Variant Builds (OSS vs Enterprise)

    Build separate doc sites from one repo. Useparams.edition and page frontmatter editionto filter content.

    YAML
    jobs:
      build:
        strategy:
          matrix:
            edition: [oss, enterprise]
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: '3.14'
              cache: 'pip'
    
          - name: Install Bengal
            run: pip install bengal
    
          - name: Build ${{ matrix.edition }} site
            run: bengal build --environment ${{ matrix.edition }} --strict --clean-output
    
          - name: Upload artifact
            uses: actions/upload-artifact@v4
            with:
              name: site-${{ matrix.edition }}
              path: public/
    

    Ensure config/environments/oss.yaml and config/environments/enterprise.yaml set params.editionaccordingly. See Multi-Variant Builds for full setup.

Alternative Platforms

GitLab CI

Create.gitlab-ci.yml:

YAML
image: python:3.14

stages:
  - build
  - deploy

build:
  stage: build
  script:
    - pip install bengal
    - bengal build --environment production --strict
  artifacts:
    paths:
      - public/

pages:
  stage: deploy
  script:
    - pip install bengal
    - bengal build --environment production --strict
  artifacts:
    paths:
      - public
  only:
    - main

Netlify

Createnetlify.toml:

TOML
[build]
  publish = "public"
  command = "pip install bengal && bengal build --environment production --strict"

[build.environment]
  PYTHON_VERSION = "3.14"

Vercel

Createvercel.json:

JSON
{
  "buildCommand": "pip install bengal && bengal build --environment production",
  "outputDirectory": "public",
  "installCommand": "pip install bengal"
}

Troubleshooting

Next Steps

✓ Track Complete