# Set up self-hosted documentation

> Learn how to set up self-hosted documentation on your own infrastructure.

<Warning title="Enterprise feature">
  This feature is available only for the [Enterprise plan](https://buildwithfern.com/pricing). To get started, reach out to [support@buildwithfern.com](mailto:support@buildwithfern.com).
</Warning>

## 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 account (for accessing the private image)

## Setup instructions

<Steps>
  <Step title="Request Access to the Docker Image">
    The self-hosted documentation runs on a private Docker image: `fernapi/fern-self-hosted`

    **Contact Fern Support** and provide your Docker Hub username to the Fern team. Fern will allowlist your account to download the private image, and provide you with the latest tag.
  </Step>

  <Step title="Download the Docker Image">
    Once your account is allowlisted, download the latest image:

    ```bash
    docker pull fernapi/fern-self-hosted:<latest-tag>
    ```

    <Warning>
      Do not use `fernapi/fern-self-hosted:latest` as it may not contain the most recent version. Always use the specific latest tag provided by Fern.
    </Warning>

    You can verify the image is available in your Docker daemon:

    ```bash
    docker images | grep fernapi/fern-self-hosted
    ```
  </Step>

  <Step title="Create a Dockerfile">
    In the directory containing your `fern/` folder, create a new `Dockerfile` with the following content:

    ```dockerfile <your-docker-file-name>
    FROM fernapi/fern-self-hosted:<latest-tag>

    COPY fern/ /fern/

    RUN fern-generate
    ```

    Replace `<latest-tag>` with the actual tag used in the previous step.

    <Info>
      `fern-generate` processes your documentation at build time, enabling faster container startup, air-gapped deployment, and a smaller attack surface. You can alternatively [defer generation to runtime](#runtime-generation).
    </Info>
  </Step>

  <Step title="Build Your Custom Docker Image">
    Build your custom Docker image using the Dockerfile:

    ```bash
    docker build -f <your-docker-file-name> -t <your-image-name> <path-to-dockerfile>
    ```

    Example:

    ```bash
    docker build -f Dockerfile -t self-hosted-docs .
    ```
  </Step>

  <Step title="Run the Documentation">
    Start your self-hosted documentation:

    ```bash
    docker run -p <port-number>:3000 <your-image-name>
    ```

    The documentation will be available at `localhost:<port-number>`.
  </Step>

  <Step title="Configure Custom Domain">
    Your documentation will be published to the domain specified in your `docs.yml` file. For example:

    ```yaml
    instances:
      - url: example-org.docs.buildwithfern.com
        custom-domain: docs.plantstore.dev
    ```
  </Step>

  <Step title="Deploy the Documentation">
    You can now deploy the image to your own infrastructure, allowing you to host the documentation on your own domain.
  </Step>
</Steps>

## Additional configuration

The following sections cover optional configurations for specific deployment scenarios.

### 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:

```dockerfile
FROM fernapi/fern-self-hosted:<latest-tag>

COPY fern/ /fern/

RUN fern-generate --only-deps
```

This starts required services (PostgreSQL, MinIO, FDR) at build time but skips documentation generation. When the container starts, it automatically runs `fern generate --docs`.

<Warning>
  Runtime generation requires network access at container startup. For air-gapped deployments, use the default build-time generation.
</Warning>

### Air-gapped deployments with gRPC

If your API uses gRPC with dependencies from the [Buf Schema Registry](https://buf.build) (BSR), the `buf` CLI fetches modules from `buf.build` during generation. This fails in air-gapped environments without network access.

<Steps>
  <Step title="Check for BSR dependencies">
    Check if your project has BSR dependencies in either location:

    **In `buf.yaml`:**

    ```yaml
    version: v2
    deps:
      - buf.build/googleapis/googleapis
      - buf.build/grpc-ecosystem/grpc-gateway
    ```

    **In `generators.yml`:**

    ```yaml
    api:
      specs:
        - proto:
            root: ./protos/
            dependencies:
              - buf.build/googleapis/googleapis
    ```

    If there's no `deps` or `dependencies` section (or only local paths), you can skip the rest of this section.
  </Step>

  <Step title="Choose a solution">
    <AccordionGroup>
      <Accordion title="Option 1: Build-time generation (recommended)" defaultOpen>
        Run `fern-generate` at build time when network access is available:

        ```dockerfile
        FROM fernapi/fern-self-hosted:<latest-tag>

        COPY fern/ /fern/

        RUN fern-generate
        ```

        This downloads BSR dependencies during the Docker build and bakes them into the image. No network access required at runtime.
      </Accordion>

      <Accordion title="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:

        ```dockerfile
        FROM fernapi/fern-self-hosted:<latest-tag>

        # 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
        ```

        <Info>
          See the [Buf documentation on dependency management](https://buf.build/docs/bsr/module/dependency-management) for more details.
        </Info>
      </Accordion>

      <Accordion title="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.

        ```yaml generators.yml
        api:
          specs:
            - proto:
                root: ./protos/
                dependencies:
                  - buf.build/googleapis/googleapis
                  - buf.build/bufbuild/protovalidate
        ```

        This approach works with both build-time and runtime generation:

        ```dockerfile
        FROM fernapi/fern-self-hosted:<latest-tag>

        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.
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>