Good infrastructure notes answer practical questions: which VLAN carries management traffic, how to restore a service, and what to check after changing a firewall rule.

Docusaurus turns Markdown and MDX files into a static documentation site. You need Node.js and dependencies to build it; the resulting files can be served by a web server without running the Node application in production.

This article combines the two earlier Docusaurus setup posts. It keeps one maintained walkthrough and corrects the earlier claims about built-in local search, automatic Traefik setup, and unconditional deployment times. The configuration below is an example, not a report of a verified deployment in my lab.

Before you begin

Use a development machine with a Node.js version supported by the Docusaurus release you select, npm, and Git. For the container section, you also need Docker Engine and the Docker Compose plugin.

1
2
3
node --version
npm --version
docker compose version

Check the current installation requirements before scaffolding. The commands intentionally use the current generator for a new project; preserve the resulting lockfile and record the selected versions. They are not a version-pinned reproduction of either original article.

Create and build the project

1
2
3
4
npx create-docusaurus@latest homelab-docs classic
cd homelab-docs
npm run build
npm run start -- --host 127.0.0.1

Use the development address printed by the command, usually http://localhost:3000. Stop it with Ctrl+C when finished. Do not expose the development server as the production service.

The classic template includes documentation, theme configuration, and example content. Keep its generated presets and configuration structure. JavaScript and TypeScript templates use different filename extensions, so confirm which files were created.

Set the production URL

For a new scaffold, the following replaces the top-level site title, URL and base path. Change docs.example.com to your intended hostname. Back up the generated configuration first:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
config_file=docusaurus.config.js
if [ ! -f "$config_file" ]; then
    config_file=docusaurus.config.ts
fi
test -f "$config_file" || { echo 'Generated configuration not found'; exit 1; }
cp -a "$config_file" "$config_file.bak-$(date +%Y%m%d-%H%M%S)"
sed -i \
  -e "s|^  title:.*|  title: 'Homelab Docs',|" \
  -e "s|^  url:.*|  url: 'https://docs.example.com',|" \
  -e "s|^  baseUrl:.*|  baseUrl: '/',|" \
  "$config_file"
sed -n '1,100p' "$config_file"

These substitutions assume the generator uses the shown two-space indentation and property names. Inspect the printed configuration and confirm all three values changed. For hosting under a subpath, use the appropriate baseUrl instead of / and adjust the web server accordingly.

Build again after configuration changes. Do not replace the entire generated configuration with a fragment for an optional plugin; that can remove the presets which provide the documentation pages.

Write a useful first runbook

Start with one service and a recovery procedure. This creates a new page without replacing the template’s introduction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mkdir -p docs/runbooks
cat > docs/runbooks/service-recovery.md <<'EOF'
---
title: Service recovery checklist
sidebar_position: 1
---

# Service recovery checklist

## Scope

Record the service, host, dependencies, and supported versions here.

## Before changing anything

- Confirm the affected host and current symptoms.
- Identify the latest usable backup and a recovery access path.
- Record the current configuration.

## Procedure

Add the exact steps for this service, including prerequisites.

## Verification

Describe the expected result and how to check it.

## Rollback

Explain how to restore the previous state.
EOF
npm run build

This is a writing template, not an executable recovery procedure. Fill in the service-specific details before anyone relies on it.

With an autogenerated sidebar covering the docs directory, the new page should appear in navigation. If your project uses a manual sidebar, add the document ID runbooks/service-recovery to that existing configuration. A sidebar entry pointing to a nonexistent document can fail the build.

Useful sections for a growing lab include networking, virtualization, services, backups, and runbooks. Keep credentials and recovery keys out of the documentation repository. A static site has no authentication boundary simply because its content feels internal.

Build a static container image

The container uses the Node major version already installed on your development machine. That version must also satisfy the generated project’s requirements. For repeatable releases, pin the build and server images to reviewed versions or digests rather than relying indefinitely on mutable tags.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
export NODE_IMAGE="node:$(node -p 'process.versions.node.split(".")[0]')-alpine"
printf 'Build image: %s\n' "$NODE_IMAGE"
cat > Dockerfile <<'EOF'
ARG NODE_IMAGE
FROM ${NODE_IMAGE} AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
EOF
cat > .dockerignore <<'EOF'
node_modules
build
.docusaurus
.git
.env
*.log
EOF
cat > nginx.conf <<'EOF'
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ $uri.html =404;
    }
}
EOF
cat > compose.yaml <<'EOF'
services:
  docs:
    build:
      context: .
      args:
        NODE_IMAGE: ${NODE_IMAGE:?Set NODE_IMAGE to a compatible Node image}
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:80"
EOF
docker compose config --quiet
docker compose up -d --build
docker compose exec -T docs nginx -t

These commands create deployment files in the project root. Back up any existing files with those names before adapting this section to an established project. The example assumes npm and an existing package-lock.json; use the matching package manager if you generated the project differently.

The site binds to the Docker host’s loopback address. That is convenient for local testing or a proxy running on the host. A reverse proxy in another container cannot reach it through that container’s own 127.0.0.1.

Verify navigation and missing pages

1
2
3
4
curl --fail --head http://127.0.0.1:3000/
curl --silent --output /dev/null --write-out '%{http_code}\n' \
  http://127.0.0.1:3000/this-page-does-not-exist
docker compose logs --tail=50 docs

Expect the home request to succeed and the invented URL to return 404. Open the runbook through the sidebar, then refresh that page directly. Check navigation and static assets in a browser too. An Nginx syntax check alone does not prove that the generated routes work.

The earlier catch-all /index.html fallback could return the homepage with a successful status for missing routes. This example instead checks generated directory and .html paths before returning a real 404.

Add a reverse proxy deliberately

The loopback deployment does not install Traefik or issue a TLS certificate. To use an existing containerized Traefik deployment, attach the docs container to its shared Docker network, remove the host port mapping if it is unnecessary, and configure a router and service for the docs container’s port 80.

Confirm the proxy’s actual Docker provider, network, entrypoint and certificate-resolver names. Labels copied from a guide do not create those prerequisites. Validate HTTPS and deep links through the real hostname after configuring the proxy.

For a managed static host, deploy the generated build directory instead. Review that host’s build settings and access controls; private repository storage does not automatically make the deployed site private.

Search, diagrams, and versioning

Search needs an integration. Docusaurus does not ship a built-in Lunr search engine just because the classic template or sidebar is enabled. Choose an integration from the search documentation, such as Algolia DocSearch where eligible or a compatible local-search plugin. Confirm plugin compatibility with your Docusaurus release and test the production build, including navigation from a search result.

Mermaid needs configuration. Follow the Mermaid documentation to install the matching theme package and merge its settings into the existing site configuration. A fenced diagram alone does not enable the integration.

Git history and published versions are different. Git records every edit. Docusaurus documentation versioning creates reader-visible snapshots. Introduce snapshots when readers genuinely need multiple supported procedures; avoid creating a new public version for every lab edit. Follow the versioning guide and add the version selector to navigation if you need one.

Publish changes through review

Keep the source, package lockfile, and deployment files in Git. Build proposed changes before merging them, review the rendered site, and deploy the approved revision. Keep the previous working image or deployment available for rollback.

The earlier article included a netcat listener that triggered rebuilds without authenticating webhook requests. Do not use that listener as a deployment mechanism. Use your hosting provider’s Git integration or a CI workflow with deliberately configured deployment credentials and host verification. Deployment time depends on the build, image cache, runner and network; there is no fixed completion time promised here.

Validation limits

This consolidated example has been reviewed for consistency and its Bash blocks can be syntax-checked without running them. It has not been built as a Docusaurus application or deployed against a live Docker host during this update. Check the current upstream requirements, build the generated project, and verify the served pages before adopting it.