postHow to deploy a Forest to GitHub Pages

So you have grown a beautiful Forester forest on your machine. It is structured, it is evergreen, and the mathematical diagrams render perfectly. Naturally, you want to share it with the world via GitHub Pages. Despite it being quite easy in the end, I spent a lovely afternoon untangling 404 errors, mysterious XSLT parsing failures, and OCaml version mismatches. So here is a short guide to share what I learned in the process, for when I will inevitably forget everything.

The website is deployed by a GitHub Actions workflow that installs a minimal TeX Live (for those sweet diagrams), set up OCaml 5.3+ (required for Forester 5.0), installs forester, builds the forest, and ship it to Pages.

I used the zauguin/install-texlive action for TeX to keep things lightweight, and the standard ocaml/setup-ocaml with caching enabled. You can see my full workflow file here: .github/workflows/forester.yml.

Installing the full TeX Live distribution on CI is a massive waste of bandwidth and time. The good thing about zauguin/install-texlive is that you can easily instruct it to pull down a minimal installation.

The package list was built by grepping around the repo for package names, and scheme-basic takes care of most of the essentials. Also keep in mind a package name in LaTeX is not necessarily the package name tlmgr expects—gotta confess that Gemini was really helpful in figuring out this one.

- name: Install TeX Live
        uses: zauguin/install-texlive@v4
        with:
          texlive_version: 2025
          packages: >
            scheme-basic standalone preview dvisvgm amsfonts amsmath
            amscls cclicenses enumitem etoolbox hyperref mathpartir
            ebproof quiver pgf spath3 mathtools microtype stmaryrd
            thmtools tikz-cd tikz-nfold xcolor

The first real pitfall I encountered was that the latest Forester (5.0+) strictly requires OCaml 5.3 or newer. If you just ask for ocaml-compiler: 5.x, you might get 5.2, which causes Opam to quietly fall back to Forester 4.3.1. Suddenly your new features vanish and you are debugging ghost bugs.

Thus we have to pin the version we need:

- name: Setup OCaml
        uses: ocaml/setup-ocaml@v3
        with:
          # Forester 5.0 requires OCaml >= 5.3.0
          ocaml-compiler: 5.3
          dune-cache: true

      - name: Install Forester
        # Pinning the version ensures we get 5.0 or fail if incompatible
        run: opam install forester.5.0

Finally, remember to add this block to give the action permission to deploy.

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

The last hurdle I had to jump was a bit of delicate configuration regarding base URLs. A good piece of debugging advice is that if you end up with a site that loads but throws "XSLT parsing errors" there is a good chance your XSLTs are 404ing, and the culprit is likely how Forester calculates base URLs when deploying to a project subdirectory. Then using browser networking tools you can see which XSL files the browser is trying to load.

All in all, keep in mind Forester is sensitive to the url setting in forest.toml and its trailing slashes. If you look at my configuration in forest.toml, you will see this:

[forest]
trees = ["trees"]
assets = ["assets"]
url = "https://mattecapu.github.io/website/"
home = "matteo-capucci/"

It's important the slashes are where they are.

Because I set the URL to include /website/, Forester infers that the base path is /website/. Consequently, it builds everything into an ulterior subfolder named after that path inside the output directory. In practice, this means that in the workflow we simply account for that:

- name: Upload artifact
  uses: actions/upload-pages-artifact@v3
  with:
    path: 'output/website/'

And that's how you are reading this!