diff --git a/fern/products/docs/pages/changelog/2026-09-01.mdx b/fern/products/docs/pages/changelog/2026-09-01.mdx index 59cd71b8b8..bf158b8517 100644 --- a/fern/products/docs/pages/changelog/2026-09-01.mdx +++ b/fern/products/docs/pages/changelog/2026-09-01.mdx @@ -5,3 +5,11 @@ You can now attach your own key-value metadata to a page with the `search-metadata` frontmatter object. Fern copies the block onto every Algolia record for that page and declares each key for faceting, so a custom search integration can group and filter results by your own taxonomy. + +## Per-deployment values in self-hosted docs + +self-hosted, customization + +A self-hosted container can now resolve environment variables in the pages it serves, on each request. A `${VAR}` whose name is listed in `FERN_RUNTIME_ENV_VARS` is deferred past generation instead of being substituted with its build-time value, so one image serves deployments that differ only in those values. + + diff --git a/fern/products/docs/pages/self-hosted/self-hosted-set-up.mdx b/fern/products/docs/pages/self-hosted/self-hosted-set-up.mdx index 532e2315e1..2efcdd1a53 100644 --- a/fern/products/docs/pages/self-hosted/self-hosted-set-up.mdx +++ b/fern/products/docs/pages/self-hosted/self-hosted-set-up.mdx @@ -76,7 +76,7 @@ RUN fern-generate ``` -`fern-generate` is a command available inside the Docker image that renders your documentation to static HTML at build time, enabling faster container startup, air-gapped deployment, and a smaller attack surface. It's not a command you run on your host machine. You can alternatively [defer generation to runtime](#runtime-generation). +`fern-generate` is a command available inside the Docker image that renders your documentation to static HTML at build time, enabling faster container startup, air-gapped deployment, and a smaller attack surface. It's not a command you run on your host machine. @@ -140,6 +140,7 @@ Configure the self-hosted container's behavior by setting environment variables | Variable | Description | Default | |---|---|---| | `CUSTOM_DOMAIN` | Override the `custom-domain` from `docs.yml` at runtime. Useful when the hostname where the docs are actually served differs from the domain in `docs.yml`. Accepts a bare hostname (e.g., `docs.plantstore.dev`); any `https://` or `http://` prefix is stripped automatically. | Value from `docs.yml` `custom-domain` | +| `FERN_RUNTIME_ENV_VARS` | Comma-separated list of variable names the container resolves in served content on each request. See [Per-deployment values](#per-deployment-values). | none | | `FERN_LOG_LEVEL` | Log level for the Fern CLI during docs generation. Options: `debug`, `info`, `warn`, `error`. | `debug` | | `NEXT_PUBLIC_BASE_PATH` | Override the base path inferred from your `docs.yml` sub-path, or serve from a sub-path without configuring `docs.yml`. The value must start with `/` and have no trailing slash (e.g., `/docs`). See [Base path](#base-path) for details. | Inferred from `docs.yml` sub-path (else serves from `/`) | @@ -247,35 +248,47 @@ Pass `NEXT_PUBLIC_BASE_PATH` when starting the container. docker run -p 3000:3000 -e NEXT_PUBLIC_BASE_PATH=/docs self-hosted-docs ``` -The base path is compiled into every URL of the static site, so a runtime base path that differs from the one the image was built with re-renders the site at startup. Re-rendering writes into the container filesystem, so it's not compatible with `readOnlyRootFilesystem: true` in Kubernetes, and it requires the image to retain the site builder: build with `FERN_KEEP_BUILD_TOOLS=1` or defer generation to runtime. +The base path is compiled into every URL of the static site, so a runtime base path that differs from the one the image was built with re-renders the site at startup. Re-rendering writes into the container filesystem, so it's not compatible with `readOnlyRootFilesystem: true` in Kubernetes, and it requires the image to retain the site builder: build with `FERN_KEEP_BUILD_TOOLS=1`. An image that retains the site builder can be re-rendered at startup for any base path, letting you reuse one image across environments that need different base paths. -### Runtime generation +### Per-deployment values -By default, `fern-generate` runs at Docker build time. Defer generation to runtime if you need to: -- Pass configuration (environment variables, secrets) at runtime -- Speed up Docker builds during development -- Share a single image across multiple documentation configurations +A value that differs per deployment, such as an API hostname, can be resolved on each request instead of at build time, so one image serves every environment. -Use the `--only-deps` flag to defer generation to runtime: +Write the value as `${VAR}` with [`settings.substitute-env-vars`](/learn/docs/configuration/site-level-settings#settingssubstitute-env-vars), and list its name in `FERN_RUNTIME_ENV_VARS` at build time. Generation rewrites a listed name to a `FERN_SELF_HOSTED_ENV_` placeholder instead of resolving it, and the container substitutes the placeholder from its own environment on every request: -```dockerfile -FROM fernenterprise/fern-self-hosted:latest +```yaml docs.yml +settings: + substitute-env-vars: true -COPY fern/ /fern/ +instances: + # Not listed, so resolved at build time. + - url: ${INSTANCE_NAME}.docs.buildwithfern.com -RUN fern-generate --only-deps +navbar-links: + - type: filled + text: Browse plants + url: ${PLANT_API}/plants ``` -This starts required services (PostgreSQL, MinIO, FDR) at build time but skips documentation generation. When the container starts, it automatically runs `fern generate --docs`. +```dockerfile +ENV FERN_RUNTIME_ENV_VARS=PLANT_API +RUN fern-generate +``` - -Runtime generation requires network access at container startup. For air-gapped deployments, use the default build-time generation. - +```bash +docker run -p 3000:3000 -e PLANT_API=api.plantstore.dev self-hosted-docs +``` + +The instance `url` and `custom-domain` always resolve at build time, since they're baked into every absolute URL. Every other text artifact the container serves is substituted, including the [Markdown](/learn/docs/ai-features/markdown) and [`llms.txt`](/learn/docs/ai-features/llms-txt) versions of each page and search results. The runtime value can include a scheme (`https://api.plantstore.dev`) or omit it (`api.plantstore.dev`). + +A name missing from `FERN_RUNTIME_ENV_VARS`, or listed with no value in the container, renders as the literal placeholder instead of an empty string. + +Page content can also spell out `FERN_SELF_HOSTED_ENV_` directly, for sources that don't use `${VAR}` substitution. ### Air-gapped deployments with gRPC @@ -326,46 +339,7 @@ RUN fern-generate This downloads BSR dependencies during the Docker build and bakes them into the image. No network access required at runtime. - - -Use this option when you don't have all the information at build time and need the docs to generate differently at runtime, such as injecting environment variables at runtime. - -To generate at runtime in an air-gapped environment, vendor buf dependencies locally: - -```dockerfile -FROM fernenterprise/fern-self-hosted:latest - -# Install buf CLI for dependency caching -RUN npm install -g @bufbuild/buf - -# Copy fern configuration -COPY fern/ fern/ -COPY protos/ protos/ - -# Pre-fetch buf dependencies at build time (caches googleapis, protovalidate) -RUN cd protos && buf dep update - -# Build fern dependencies -RUN fern-generate --only-deps -``` - -Update `buf.yaml` to reference vendored dependencies: - -```yaml -# Before -deps: - - buf.build/googleapis/googleapis - -# After -deps: - - ./vendor/googleapis -``` - - -See the [Buf documentation on dependency management](https://buf.build/docs/bsr/module/dependency-management) for more details. - - - + If you don't have a `buf.yaml` file, you can specify proto dependencies directly in your `generators.yml`. The self-hosted container automatically creates a temporary `buf.yaml` from these dependencies during the build process. @@ -379,18 +353,12 @@ api: - buf.build/bufbuild/protovalidate ``` -This approach works with both build-time and runtime generation: - ```dockerfile FROM fernenterprise/fern-self-hosted:latest COPY fern/ /fern/ -# For build-time generation (recommended for air-gapped deployments) RUN fern-generate - -# Or for runtime generation (requires network at startup) -# RUN fern-generate --only-deps ``` The container parses all `generators.yml` files in your fern directory, finds proto specs with dependencies but no `buf.yaml`, and creates the necessary configuration automatically.