Set up self-hosted documentation
Enterprise feature
This feature is available only for the Enterprise plan. To get started, reach out to support@buildwithfern.com.
Prerequisites
Before setting up self-hosted documentation, ensure you have:
- Docker installed on your system
- Access to your Fern project’s
fern/directory - A Docker Hub organization access token (OAT) from Fern (for pulling the private image)
Setup instructions
Authenticate with Docker Hub
The self-hosted documentation runs on a private Docker image: fernenterprise/fern-self-hosted
Contact Fern Support to receive a Docker Hub organization access token (OAT).
Log in to Docker Hub using the token provided by Fern:
When prompted for a password, enter the OAT provided by the Fern team.
In CI, pass the token via the DOCKERHUB_OAT environment variable:
Create a Dockerfile
In the same directory that contains your fern/ folder, create a file named Dockerfile:
Add the following content to the Dockerfile:
fern-generate is a command available inside the Docker image that processes your documentation 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.
Build your Docker image
From the directory containing your Dockerfile and fern/ folder, build the image:
Run the documentation
Start your self-hosted documentation:
The documentation will be available at localhost:3000.
Deploy the documentation
You can now deploy the image to your own infrastructure, allowing you to host the documentation on your own domain.
Once deployed, you can set up preview environments to preview documentation changes on every pull request.
Custom domain
Your documentation uses the domain specified in your docs.yml file. For example:
To override the domain at runtime (for example, when the actual hostname differs from the custom-domain in docs.yml), set the CUSTOM_DOMAIN environment variable:
See Environment variables for details.
Environment variables
Configure the self-hosted container’s behavior by setting environment variables in your Dockerfile or Kubernetes deployment.
General
Cache warmup
The container can pre-fetch all pages on startup to ensure the first real user request is fast.
Cache proxy
The container includes a caching proxy that sits in front of the Next.js server.
Cross-origin resource sharing (CORS) proxy
The container includes a CORS proxy that allows the documentation frontend to make cross-origin requests (e.g., to your API for the API Explorer’s Try it feature). By default, only the docs domain itself is allowed. Use CORS_PROXY_ALLOWED_DOMAINS to allowlist additional domains.
For example, to allow requests to api.plantstore.dev and auth.plantstore.dev:
To allow multiple domains:
Debugging
On-page feedback
In self-hosted mode, on-page feedback events are emitted as structured JSON logs to the container’s stdout, prefixed with [fern-docs-feedback]. You can filter for these in your logging infrastructure:
Each log line contains an event name, a timestamp, and a set of properties:
Tracked events
Properties
Additional configuration
The following sections cover optional configurations for specific deployment scenarios.
Base path
By default, the self-hosted container serves documentation from root (/). Set NEXT_PUBLIC_BASE_PATH to serve from a sub-path instead, such as /docs. This is useful when your documentation shares a domain with other applications behind a reverse proxy.
The value must start with / and must not end with a trailing slash.
Build-time (recommended)
Runtime
Set NEXT_PUBLIC_BASE_PATH in your Dockerfile before running fern-generate. This patches the base path into the bundle at build time, so the container can run with a read-only filesystem.
With NEXT_PUBLIC_BASE_PATH=/docs, the documentation is accessible at http://localhost:3000/docs instead of http://localhost:3000/.
A single Docker image built without NEXT_PUBLIC_BASE_PATH can be configured at runtime to serve from any path. This lets you reuse one image across environments that may need different base paths.
Runtime generation
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
Use the --only-deps flag to defer generation to runtime:
This starts required services (PostgreSQL, MinIO, FDR) at build time but skips documentation generation. When the container starts, it automatically runs fern generate --docs.
Runtime generation requires network access at container startup. For air-gapped deployments, use the default build-time generation.
Air-gapped deployments with gRPC
If your API uses gRPC with dependencies from the Buf Schema Registry (BSR), the buf CLI fetches modules from buf.build during generation. This fails in air-gapped environments without network access.
Check for BSR dependencies
Check if your project has BSR dependencies in either location:
In buf.yaml:
In generators.yml:
If there’s no deps or dependencies section (or only local paths), you can skip the rest of this section.
Choose a solution
Option 1: Build-time generation (recommended)
Run fern-generate at build time when network access is available:
This downloads BSR dependencies during the Docker build and bakes them into the image. No network access required at runtime.
Option 2: Vendor dependencies for runtime generation
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:
Update buf.yaml to reference vendored dependencies:
See the Buf documentation on dependency management for more details.
Option 3: Specify dependencies in generators.yml
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.
This approach works with both build-time and runtime generation:
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.
Kubernetes deployment
Here is a sample Deployment and Service configuration. Replace your-registry/fern-docs:latest with your image name.
Apply the configuration:
deployment.yaml:
service.yaml:
For health check endpoint details, see Health check endpoints.