Skip to main content

quarto-publish.yml

Render a Quarto project and deploy it to the gh-pages branch, which GitHub Pages serves. One-time setup: set Settings -> Pages -> Source to “Deploy from a branch”, branch gh-pages / (root). Render-only callers (deploy: false) skip the deploy.

Inputs

Input Type Default Description
runs-on string empty (CI_RUNNER variable, else ubuntu-latest) Runner label for every job. Use a self-hosted label only for a private repository, never a public one. A self-hosted runner without root cannot run steps that call apt or sudo.
path string '.' Path in the caller repo to the Quarto project to render (the directory with _quarto.yml).
setup-r boolean false Install R and project R dependencies before rendering (for R/knitr Quarto projects).
r-packages string (long; see workflow file) r-lib/actions package specs installed when setup-r is true and use-renv is false. Defaults to any::knitr and any::rmarkdown.
use-renv boolean false Restore R dependencies with renv instead of r-packages (requires setup-r).
install-package boolean false Run R CMD INSTALL <path> before rendering so package functions are available (requires setup-r).
setup-chrome boolean false Install Chrome via browser-actions/setup-chrome before rendering (needed for revealjs/screenshots/mermaid).
tinytex boolean false Install TinyTeX (needed to render PDF outputs).
setup-julia boolean false Install Julia (julia-actions/setup-julia) before rendering, for pages that execute {julia} chunks; under knitr these run through the R package JuliaCall, so add any::JuliaCall to r-packages (with setup-r). The Julia depot is cached between runs via julia-actions/cache. When JuliaCall is installed, its Julia dependencies (RCall, Suppressor) are set up in their own step before the render, and R_LD_LIBRARY_PATH is set to R’s own lib directory so Julia started by R does not load the system libunwind, which segfaults on any Julia exception. @v3 and later.
julia-version string '1' Julia version to install, in setup-julia’s version syntax. Only used when setup-julia is true. @v3 and later.
preinstalled-toolchain boolean false The runner already provides R, Quarto, TinyTeX and Julia and has no sudo, as a self-hosted runner does. Skips the steps that install them (apt packages, Chrome, Quarto, R, Julia and its cache) and installs r-packages with pak into the runner’s user library. With an empty r-packages nothing is installed. The runner must already have R with the pak and tinytex packages, Quarto, TinyTeX, Julia and (if needed) Chrome; tinytex is then ignored for the install. renv, the JuliaCall setup and the freeze cache still run.
apt-packages string '' Extra apt packages to install before rendering (space-separated).
output-dir string '_site' Directory the rendered site is written to, relative to path; forwarded to quarto render --output-dir and overrides any _quarto.yml setting.
render-profile string '' Quarto --profile to render with (e.g. 'website'). Empty to omit.
formats string '' Whitespace- or newline-separated Quarto output formats to render individually in order to prevent V8 out-of-memory errors on large sites.
per-file-formats string '' Whitespace- or newline-separated formats (e.g. pdf) to render one file at a time, each in its own process, after the normal render. Prevents Deno heap exhaustion on a large PDF pass; list the other formats in formats.
freeze-cache boolean false Restore and save Quarto _freeze directory via actions/cache keyed on renv.lock and commit SHA. After the restore, frozen results of pages whose file, included subfiles or named data files changed since the cached commit are removed. The saved cache also seeds PR previews.
freeze-cache-save-on-failure boolean false Also save _freeze when the render fails, so a re-run resumes from the pages already executed. Only used when freeze-cache is true. A run killed outright, by a timeout or a lost runner, may not get to save.
deno-v8-options string '--max-old-space-size=12288,--max-heap-size=12288' Options passed to Deno V8 via QUARTO_DENO_V8_OPTIONS to raise the heap limit for large site renders.
fail-on-render-warning boolean false Fail the build if Quarto emits any render warnings (WARN in the render log).
forbid-log-patterns string '' Newline-separated fixed string patterns that fail the build if found in the render log (e.g. missing listing files).
timezone string '' IANA time zone (e.g. America/Los_Angeles) exported as TZ for the render and later steps, so date: last-modified stamps are not in UTC. Empty leaves the runner’s time zone unchanged. A value containing a newline or = is rejected.
timeout-minutes number 45 Timeout, in minutes, for the render (build job). The default is the hang ceiling, not a budget; raise it for a site whose full render (every page in every format) takes longer. A caller cannot set timeout-minutes on a job that uses uses:.
checkout-submodules boolean false Initialize the caller’s git submodules before rendering.
pre-render-artifact string '' Name of a workflow-run artifact to download into the project before rendering, so an earlier job can inject build-time assets. Empty to skip.
pre-render-artifact-path string '' Directory in the caller repo to extract pre-render-artifact into. Defaults to the workspace root.
deploy boolean true Deploy the rendered site to the gh-pages branch. Set false to render only.

Secrets

Secret Required Description
SUBMODULES_TOKEN no Token with read access to private submodules (only needed when checkout-submodules is true).

Permissions

Grant contents: write (the deploy job pushes to gh-pages). Grant it even with deploy: false: a reusable workflow’s caller must grant a job’s permissions even when that job is skipped, and the deploy job is part of the workflow. The build job downscopes its own token to contents: read.

Example

Do not name a concurrency: group gh-pages anywhere in your caller workflow – neither a top-level block nor one on the calling job, which deadlock identically. Any other group name is fine, and is how you serialize the caller’s own runs. quarto-publish.yml serializes gh-pages deploys internally on its deploy job (group: gh-pages). A caller-level block with the same group name deadlocks GitHub Actions against the nested job: deploy fails with no runner, no steps, and no log, and the site silently stops publishing (gha#809). The example stub used to name that group, from before the job-level one existed; it now names quarto-publish-${{ github.ref }} instead, keeping the serialization without the collision.

# Copy to .github/workflows/quarto-publish.yml in your repo.
#
# Renders a Quarto site and deploys it to the gh-pages branch (which GitHub
# Pages serves). The calling job must grant contents: write so the deploy step
# can push to gh-pages (repos default to a read-only token).
#
# One-time repo setup: Settings -> Pages -> Build and deployment -> Source =
# "Deploy from a branch", branch `gh-pages` / `(root)`.
name: Quarto Publish

on:
  push:
    branches: [main]
  workflow_dispatch:
  # For a render-only build check (deploy: false below), also add:
  # pull_request:

# Serialize deploys so two pushes don't race on the gh-pages branch. Named
# for this workflow rather than `gh-pages`, which the called workflow's own
# deploy job already holds -- reusing that name deadlocks the two.
concurrency:
  group: quarto-publish-${{ github.ref }}
  cancel-in-progress: false

jobs:
  publish:
    permissions:
      # Push the rendered site to gh-pages. Grant this even with deploy: false
      # (render only): the workflow's deploy job declares contents: write, and a
      # reusable workflow's caller must grant a job's permissions even when that
      # job is skipped. The build job downscopes its own token to read.
      contents: write
    uses: Morrison-Lab/gha/.github/workflows/quarto-publish.yml@v2
    # with:
    #   path: website           # Quarto project lives in a subdir (default: repo root)
    #   setup-r: true           # for R/knitr Quarto projects (default: false)
    #   use-renv: true          # restore R deps with renv instead of r-packages
    #   install-package: true   # R CMD INSTALL <path> before render (for R packages)
    #   setup-chrome: true      # install Chrome for revealjs/screenshots/mermaid
    #   tinytex: true           # needed to render PDF outputs
    #   setup-julia: true       # run {julia} chunks (add any::JuliaCall to r-packages; needs @v3)
    #   preinstalled-toolchain: true       # self-hosted runner with R, Quarto, TinyTeX, Julia installed
    #   julia-version: '1'
    #   apt-packages: ''        # extra system packages (space-separated)
    #   output-dir: docs        # render to a dir other than the default _site (overrides _quarto.yml)
    #   render-profile: website # Quarto --profile to render with
    #   formats: 'pdf docx revealjs html' # per-format render split to prevent V8 OOM
    #   per-file-formats: pdf   # render these formats one file at a time (list the others in formats)
    #   freeze-cache: true      # cache Quarto _freeze across runs
    #   freeze-cache-save-on-failure: true  # keep it when the render fails
    #   deno-v8-options: '--max-old-space-size=12288,--max-heap-size=12288'
    #   fail-on-render-warning: true # fail if Quarto emits WARN in render log
    #   forbid-log-patterns: |  # fail on specific forbidden substrings in render log
    #     doesn't match any files or folders
    #   timezone: America/Los_Angeles # export TZ for the render (e.g. for date: last-modified)
    #   timeout-minutes: 90 # raise the 45-minute render cap for a site that renders slowly
    #   checkout-submodules: true
    #   pre-render-artifact: demo-media       # inject a prior job's artifact before render
    #   pre-render-artifact-path: website/media
    #   deploy: false           # render only (e.g. a build check); skips the gh-pages push
    #                           # (still grant contents: write; the deploy job is declared)
    # secrets:
    #   SUBMODULES_TOKEN: ${{ secrets.SUBMODULES_TOKEN }}

See examples/quarto-publish.yml for the full caller stub.