*** title: Previews description: >- Set up preview environments for your self-hosted documentation using containers or static exports. availability: beta ------------------ For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see [https://buildwithfern.com/learn/llms.txt](https://buildwithfern.com/learn/llms.txt). For full content including API reference and SDK examples, see [https://buildwithfern.com/learn/llms-full.txt](https://buildwithfern.com/learn/llms-full.txt). 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). There are two ways to set up preview environments for your self-hosted docs: * **Container-based** — deploys the same self-hosted Docker container you use in production, giving you a full-fidelity preview with search, API Explorer, and authentication. * **Static export** — renders your docs to static HTML and assets that can be served from any object store (S3, GCS, R2), with no running containers required. Both approaches render content exactly as it would appear in production. ## Choosing an approach For most teams, **static export** is the best starting point because of its minimal infrastructure and setup. Choose **container-based** if you need search, API Explorer, or authentication in your previews. | | Container-based | Static export Beta | | ------------------------- | ---------------------------------------------- | ---------------------------------------------------------- | | **Content rendering** | Identical to production | Identical to production | | **Search** | Yes | No | | **API Explorer (Try it)** | Yes | No | | **Authentication** | Yes | No | | **Infrastructure** | Requires container hosting, load balancer, DNS | Serves from any object store (S3, GCS, R2) | | **Scaling** | One container per preview | Thousands of previews with no servers | | **Cost** | Higher | Low | | **Cleanup** | Must tear down containers and resources | Simple — delete static files | ## GitHub Actions workflows The following workflows can be added to your repository to automatically build and deploy preview environments on every pull request. This workflow builds the Docker image on each pull request and deploys it to your container hosting platform. ```yaml title=".github/workflows/preview-docs-container.yml" name: preview-docs-container on: pull_request: jobs: preview-docs: runs-on: ubuntu-latest permissions: write-all steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build docs container run: docker build -t self-hosted-docs:${{ github.sha }} . - name: Push container to registry run: | docker tag self-hosted-docs:${{ github.sha }} ${{ secrets.REGISTRY }}/self-hosted-docs:${{ github.sha }} docker push ${{ secrets.REGISTRY }}/self-hosted-docs:${{ github.sha }} - name: Deploy preview run: | # Deploy the container to your hosting platform # (e.g. ECS, Cloud Run, Kubernetes, etc.) # and retrieve the preview URL echo "Deploy self-hosted-docs:${{ github.sha }} to your platform" ``` This workflow builds the container, runs the static export, and uploads the output to S3. Adapt the upload step for your object store. ```yaml title=".github/workflows/preview-docs-static.yml" name: preview-docs-static on: pull_request: jobs: preview-docs: runs-on: ubuntu-latest permissions: write-all steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build docs container run: docker build -t self-hosted-docs . - name: Start container run: | docker run -d \ --name docs-container \ -e WARMUP=true \ self-hosted-docs - name: Wait for container to be healthy run: | echo "Waiting for container to be healthy..." MAX_ATTEMPTS=60 ATTEMPT=0 while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do ATTEMPT=$((ATTEMPT + 1)) if docker exec docs-container sh -c \ 'TOKEN=$(cat /tmp/.cache-admin-token 2>/dev/null); \ curl -f -s --max-time 5 \ -H "Authorization: Bearer $TOKEN" \ http://localhost:3000/__cache/stats' > /dev/null 2>&1; then echo "Container is healthy." break fi echo " Not ready yet... ($ATTEMPT/$MAX_ATTEMPTS)" sleep 5 done if [ "$ATTEMPT" -ge "$MAX_ATTEMPTS" ]; then echo "Error: container did not become healthy." exit 1 fi - name: Export static site run: | docker exec docs-container /scripts/export.sh docker cp docs-container:/tmp/fern-static-export.tar.gz ./export.tar.gz - name: Extract static files run: | mkdir -p ./site tar -xzf export.tar.gz -C ./site - name: Upload to S3 uses: jakejarvis/s3-sync-action@v0.5.1 with: args: --delete env: SOURCE_DIR: ./site AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION }} DEST_DIR: pr-${{ github.event.pull_request.number }} - name: Comment preview URL on PR if: github.event_name == 'pull_request' uses: thollander/actions-comment-pull-request@v3 with: message: | Preview: http://${{ secrets.AWS_S3_BUCKET }}.s3-website.${{ secrets.AWS_REGION }}.amazonaws.com/pr-${{ github.event.pull_request.number }} pr-number: ${{ github.event.pull_request.number }} - name: Stop container if: always() run: docker rm -f docs-container ```