Docusaurus: Customization, Kubernetes and CICD

Published: Nov 30, 2023 by Isaac Johnson

We had so much fun with Docusaurus in that last post I figured it would be good idea to keep going. Last time we set up an initial Docusaurus instance locally, created some entries in blogs and docs then containerized the whole thing. I manually built and pushed to Harbor CR before working out some Kubernetes deployments and CronJobs.

The gaps, I see them include:

  • We still have the ‘getting started’ landing page which looks pretty cheesy
  • Building by hand is never a good pattern - let’s setup some form of Gitea/Github action.
  • I would love to explore ways to post some candidate versions - like a pre-release.

The landing page

Let’s tackle this first.

We can change the images, theme colours and more

/content/images/2023/11/docusaurus2-01.png

Most of the main configuration elements like Name, title and logo can be changed in the docusaurus.config.js file

builder@LuiGi17:~/Workspaces/docuSite/my-fb-website$ git diff docusaurus.config.js
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 7b8d5b1..258566a 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -8,20 +8,20 @@ import {themes as prismThemes} from 'prism-react-renderer';

 /** @type {import('@docusaurus/types').Config} */
 const config = {
-  title: 'My Site',
-  tagline: 'Dinosaurs are cool',
+  title: 'Freshbrewed Documentation',
+  tagline: 'Docs from Cloudy McCloudface',
   favicon: 'img/favicon.ico',

   // Set the production url of your site here
-  url: 'https://your-docusaurus-site.example.com',
+  url: 'https://docusaurus.freshbrewed.science',
   // Set the /<baseUrl>/ pathname under which your site is served
   // For GitHub pages deployment, it is often '/<projectName>/'
   baseUrl: '/',

   // GitHub pages deployment config.
   // If you aren't using GitHub pages, you don't need these.
-  organizationName: 'facebook', // Usually your GitHub org/user name.
-  projectName: 'docusaurus', // Usually your repo name.
+  organizationName: 'idjohnson', // Usually your GitHub org/user name.
+  projectName: 'idjohnson', // Usually your repo name.

   onBrokenLinks: 'throw',
   onBrokenMarkdownLinks: 'warn',
@@ -66,21 +66,21 @@ const config = {
       // Replace with your project's social card
       image: 'img/docusaurus-social-card.jpg',
       navbar: {
-        title: 'My Site',
+        title: 'Freshbrewed',
         logo: {
-          alt: 'My Site Logo',
-          src: 'img/logo.svg',
+          alt: 'FB Logo',
+          src: 'img/builder.png',
         },
         items: [
           {
             type: 'docSidebar',
:...skipping...
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 7b8d5b1..258566a 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -8,20 +8,20 @@ import {themes as prismThemes} from 'prism-react-renderer';

 /** @type {import('@docusaurus/types').Config} */
 const config = {
-  title: 'My Site',
-  tagline: 'Dinosaurs are cool',
+  title: 'Freshbrewed Documentation',
+  tagline: 'Docs from Cloudy McCloudface',
   favicon: 'img/favicon.ico',

   // Set the production url of your site here
-  url: 'https://your-docusaurus-site.example.com',
+  url: 'https://docusaurus.freshbrewed.science',
   // Set the /<baseUrl>/ pathname under which your site is served
   // For GitHub pages deployment, it is often '/<projectName>/'
   baseUrl: '/',

   // GitHub pages deployment config.
   // If you aren't using GitHub pages, you don't need these.
-  organizationName: 'facebook', // Usually your GitHub org/user name.
-  projectName: 'docusaurus', // Usually your repo name.
+  organizationName: 'idjohnson', // Usually your GitHub org/user name.
+  projectName: 'idjohnson', // Usually your repo name.

   onBrokenLinks: 'throw',
   onBrokenMarkdownLinks: 'warn',
@@ -66,21 +66,21 @@ const config = {
       // Replace with your project's social card
       image: 'img/docusaurus-social-card.jpg',
       navbar: {
-        title: 'My Site',
+        title: 'Freshbrewed',
         logo: {
-          alt: 'My Site Logo',
-          src: 'img/logo.svg',
+          alt: 'FB Logo',
+          src: 'img/builder.png',
         },
         items: [
           {
             type: 'docSidebar',
             sidebarId: 'tutorialSidebar',
             position: 'left',
-            label: 'Tutorial',
+            label: 'Docs',
           },
           {to: '/blog', label: 'Blog', position: 'left'},
           {
-            href: 'https://github.com/facebook/docusaurus',
+            href: 'https://github.com/idjohnson/idjohnson',
             label: 'GitHub',
             position: 'right',
           },
@@ -93,8 +93,8 @@ const config = {
             title: 'Docs',
             items: [
               {
-                label: 'Tutorial',
-                to: '/docs/intro',
+                label: 'Playbooks',
+                to: '/docs/category/playbooks',
               },
             ],
           },

That updated the top banner

/content/images/2023/11/docusaurus2-02.png

The three images and descriptions on the main page are configured in src/components/HomepageFeatures/index.js. We don’t have to keep 3, but I did.

Because the FeatureList, as setup, wants an SVG image, or scaled vector graphics, I used a free Adobe util page to convert MJ images to SVGs: https://new.express.adobe.com/tools/convert-to-svg

$ git diff src/components/HomepageFeatures/index.js
diff --git a/src/components/HomepageFeatures/index.js b/src/components/HomepageFeatures/index.js
index acc7621..3a40441 100644
--- a/src/components/HomepageFeatures/index.js
+++ b/src/components/HomepageFeatures/index.js
@@ -4,32 +4,29 @@ import styles from './styles.module.css';

 const FeatureList = [
   {
-    title: 'Easy to Use',
-    Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
+    title: 'Fun to Trash',
+    Svg: require('@site/static/img/mydinomean.svg').default,
     description: (
       <>
-        Docusaurus was designed from the ground up to be easily installed and
-        used to get your website up and running quickly.
+        While it is fun to let our Dinos tear about our hardware, sometimes it's better to use automation tools like AWX.
       </>
     ),
   },
   {
-    title: 'Focus on What Matters',
-    Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
+    title: 'On Call is Great',
+    Svg: require('@site/static/img/mydinoheadphones.svg').default,
     description: (
       <>
-        Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
-        ahead and move your docs into the <code>docs</code> directory.
+        Our Dinos love to chat on Zoom and Teams, but then they don't get anything done.  Maybe it might be better to file a ticket
       </>
     ),
   },
   {
-    title: 'Powered by React',
-    Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
+    title: 'Sharing Our Toys',
+    Svg: require('@site/static/img/mydinotoys.svg').default,
     description: (
       <>
-        Extend or customize your website layout by reusing React. Docusaurus can
-        be extended while reusing the same header and footer.
+        Dinos love to play and Dinos love to share.  Go here to share your toys!
       </>
     ),
   },

which resulted in

/content/images/2023/11/docusaurus2-03.png

The colour schemes are in defined in src/css/custom.css

$ git diff src/css/custom.css
diff --git a/src/css/custom.css b/src/css/custom.css
index 2bc6a4c..c9a62c0 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -6,25 +6,25 @@

 /* You can override the default Infima variables here. */
 :root {
-  --ifm-color-primary: #2e8555;
-  --ifm-color-primary-dark: #29784c;
-  --ifm-color-primary-darker: #277148;
-  --ifm-color-primary-darkest: #205d3b;
-  --ifm-color-primary-light: #33925d;
-  --ifm-color-primary-lighter: #359962;
-  --ifm-color-primary-lightest: #3cad6e;
+  --ifm-color-primary: #852e2e;
+  --ifm-color-primary-dark: #782929;
+  --ifm-color-primary-darker: #712727;
+  --ifm-color-primary-darkest: #5d2020;
+  --ifm-color-primary-light: #923333;
+  --ifm-color-primary-lighter: #993535;
+  --ifm-color-primary-lightest: #ad3c3c;
   --ifm-code-font-size: 95%;
   --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
 }

 /* For readability concerns, you should choose a lighter palette in dark mode. */
 [data-theme='dark'] {
-  --ifm-color-primary: #25c2a0;
-  --ifm-color-primary-dark: #21af90;
-  --ifm-color-primary-darker: #1fa588;
-  --ifm-color-primary-darkest: #1a8870;
-  --ifm-color-primary-light: #29d5b0;
-  --ifm-color-primary-lighter: #32d8b4;
-  --ifm-color-primary-lightest: #4fddbf;
+  --ifm-color-primary: #c22525;
+  --ifm-color-primary-dark: #af2121;
+  --ifm-color-primary-darker: #a51f1f;
+  --ifm-color-primary-darkest: #881a1a;
+  --ifm-color-primary-light: #d52929;
+  --ifm-color-primary-lighter: #d83232;
+  --ifm-color-primary-lightest: #dd4f4f;
   --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
 }

And the middle link is configured in the main ‘index.js’ page

$ git diff src/pages/index.js
diff --git a/src/pages/index.js b/src/pages/index.js
index a8c61f2..ff4c1cc 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -19,8 +19,8 @@ function HomepageHeader() {
         <div className={styles.buttons}>
           <Link
             className="button button--secondary button--lg"
-            to="/docs/intro">
-            Docusaurus Tutorial - 5min ⏱️
+            to="/docs/category/playbooks">
+            (⊙_☉) Read The _Fun_ Manaual 🤷🏽
           </Link>
         </div>
       </div>
@@ -32,7 +32,7 @@ export default function Home() {
   const {siteConfig} = useDocusaurusContext();
   return (
     <Layout
-      title={`Hello from ${siteConfig.title}`}
+      title={`Greetings from ${siteConfig.title}`}
       description="Description will go into a meta tag in <head />">
       <HomepageHeader />
       <main>

/content/images/2023/11/docusaurus2-04.png

I’ll make an MR to come back to later

/content/images/2023/11/docusaurus2-05.png

CICD

Now that it looks a bit nicer, let’s sort out our CICD

I’ll make a dir to hold the workflows

builder@LuiGi17:~/Workspaces/docuSite/my-fb-website$ code .
builder@LuiGi17:~/Workspaces/docuSite/my-fb-website$ mkdir -p .gitea/workflows

I’ll want to enable Repo Actions

/content/images/2023/11/docusaurus2-06.png

While I don’t expect them to work, I first added the actions to main

/content/images/2023/11/docusaurus2-07.png

name: CICD
run-name: $ building docusaurus
on: [push]

jobs:
  BuildAndPush:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: dockerBuild
        run: |
          docker build --target development -t docusaurus:dev099 .
      - name: dockerTag
        run: |
          docker tag docs:dev099 harbor.freshbrewed.science/freshbrewedprivate/docusaurus:dev099
      - name: dockerTag
        run: |
          docker push harbor.freshbrewed.science/freshbrewedprivate/docusaurus:dev099


If we wish to test a build without adding more changes, we can always push an empty commit

builder@LuiGi17:~/Workspaces/docuSite/my-fb-website$ git commit --allow-empty -m "test"
[main 2de9208] test
builder@LuiGi17:~/Workspaces/docuSite/my-fb-website$ git push
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 184 bytes | 184.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To https://gitea.freshbrewed.science/builder/MyFbWebsite.git
   34f1e6d..2de9208  main -> main

This triggered the Gitea action

/content/images/2023/11/docusaurus2-08.png

One of my challenges as I attempted to debug is that my particular containerized Gitea agent seems to not be able to access the docker socket. This means it’s not running in priveldged mode.

/content/images/2023/11/docusaurus2-09.png

I have the option to try to enable that, or mount to the local filesystem (for docker.sock). I could pivot and add a Gitea agent on a physical host or VM with docker, or I could switch to Kaniko which doesn’t require access to docker.sock.

Kaniko is far more successful:

name: CICD
run-name: $ building docusaurus
on: [push]

jobs:
  BuildAndPush:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: apt
        run: |
          # if running as non-root, add sudo
          apt update
          apt install -y apt-utils
      - name: Kaniko build
        uses: aevea/action-kaniko@master
        with:
          registry: harbor.freshbrewed.science
          image: freshbrewedprivate/docusaurus
          username: kaniko
          password: $
          cache: true
          cache_registry: node:lts

/content/images/2023/11/docusaurus2-10.png

To get it to build with Kaniko, I did need to comment out the user / group usage. I cannot explain it, but it refused to add users but would also claim the users did not exist

## Base ########################################################################
# Use a larger node image to do the build for native deps (e.g., gcc, python)
FROM node:lts as base

ENV GROUP_ID=1000 \
    USER_ID=1000

# Reduce npm log spam and colour during install within Docker
ENV NPM_CONFIG_LOGLEVEL=warn
ENV NPM_CONFIG_COLOR=false

#USER root
#RUN addgroup --gid $GROUP_ID node
#RUN adduser --shell /bin/bash --uid $USER_ID --gid $GROUP_ID node

# We'll run the app as the `node` user, so put it in their home directory
WORKDIR /home/node/app
# Copy the source code over
COPY --chown=node:node . /home/node/app/

## Development #################################################################
# Define a development target that installs devDeps and runs in dev mode
FROM base as development

WORKDIR /home/node/app
# Install (not ci) with dependencies, and for Linux vs. Linux Musl (which we use for -alpine)
RUN npm install
# Switch to the node user vs. root
#RUN chown -R node:node /home/node/

#USER node
# Expose port 3000
EXPOSE 3000
# Start the app in debug mode so we can attach the debugger
CMD ["npm", "start"]

## Production ##################################################################
# Also define a production target which doesn't use devDeps
#FROM base as production
#WORKDIR /home/node/app
#COPY --from=development /home/node/app/node_modules /home/node/app/node_modules
# Build the Docusaurus app
#RUN npm run build

## Deploy ######################################################################
# Use a stable nginx image
#FROM nginx:stable-alpine as deploy
#WORKDIR /home/node/app
# Copy what we've installed/built from production
#COPY --from=production /home/node/app/build /usr/share/nginx/html/

This then built

/content/images/2023/11/docusaurus2-11.png

I then uncommented those blocks

## Base ########################################################################
# Use a larger node image to do the build for native deps (e.g., gcc, python)
FROM node:lts as base

ENV GROUP_ID=1000 \
    USER_ID=1000

# Reduce npm log spam and colour during install within Docker
ENV NPM_CONFIG_LOGLEVEL=warn
ENV NPM_CONFIG_COLOR=false

#USER root
#RUN addgroup --gid $GROUP_ID node
#RUN adduser --shell /bin/bash --uid $USER_ID --gid $GROUP_ID node

# We'll run the app as the `node` user, so put it in their home directory
WORKDIR /home/node/app
# Copy the source code over
COPY --chown=node:node . /home/node/app/

## Development #################################################################
# Define a development target that installs devDeps and runs in dev mode
FROM base as development

WORKDIR /home/node/app
# Install (not ci) with dependencies, and for Linux vs. Linux Musl (which we use for -alpine)
RUN npm install
# Switch to the node user vs. root
#RUN chown -R node:node /home/node/

#USER node
# Expose port 3000
EXPOSE 3000
# Start the app in debug mode so we can attach the debugger
CMD ["npm", "start"]

## Production ##################################################################
# Also define a production target which doesn't use devDeps
FROM base as production
WORKDIR /home/node/app
COPY --from=development /home/node/app/node_modules /home/node/app/node_modules
# Build the Docusaurus app
RUN npm run build

## Deploy ######################################################################
# Use a stable nginx image
FROM nginx:stable-alpine as deploy
WORKDIR /home/node/app
# Copy what we've installed/built from production
COPY --from=production /home/node/app/build /usr/share/nginx/html/

and added the target in the build file (CICD.yaml)

name: CICD
run-name: $ building docusaurus
on: [push]

jobs:
  BuildAndPush:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: apt
        run: |
          # if running as non-root, add sudo
          apt update
          apt install -y apt-utils
      - name: add Node Usr
        run: |
          export GROUP_ID=1111
          export USER_ID=1111
          addgroup --gid $GROUP_ID node || true
          adduser -D --uid $USER_ID -G node node || true
      - name: Kaniko build
        uses: aevea/action-kaniko@master
        with:
          registry: harbor.freshbrewed.science
          image: freshbrewedprivate/docusaurus
          username: kaniko
          password: $
          cache: true
          cache_registry: node:lts
          target: development

which built and pushed

/content/images/2023/11/docusaurus2-12.png

I can double check Harbor and see that SHA has the ‘main’ tag attached (the branch)

/content/images/2023/11/docusaurus2-13.png

I’ll now edit the deployment to try it

builder@LuiGi17:~/Workspaces/PublicResume$ kubectl get deployment docusaurus-deployment -o yaml | grep -i image:
      - image: harbor.freshbrewed.science/freshbrewedprivate/docusaurus:dev03
builder@LuiGi17:~/Workspaces/PublicResume$ kubectl edit deployment docusaurus-deployment
deployment.apps/docusaurus-deployment edited
builder@LuiGi17:~/Workspaces/PublicResume$ kubectl get deployment docusaurus-deployment -o yaml | grep -i image:
      - image: harbor.freshbrewed.science/freshbrewedprivate/docusaurus:main

I can then see it come up (after I restored a missing CM)

builder@LuiGi17:~/Workspaces/PublicResume$ kubectl get pods | grep doc
docusaurus-deployment-7b57b4fdcc-8plm8                   1/1     Running            0                 6m9s
docusaurus-deployment-677b66659b-j79rf                   0/1     Terminating        0                 3d10h
builder@LuiGi17:~/Workspaces/PublicResume$ kubectl get pods | grep doc
docusaurus-deployment-7b57b4fdcc-8plm8                   1/1     Running            0                 6m22s

And just based on the UI we can see it was updated

/content/images/2023/11/docusaurus2-14.png

Dual builds

I can then change the CICD file to pack in the build number and dev and prod

name: CICD
run-name: $ building docusaurus
on: [push]

jobs:
  BuildAndPush:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: apt
        run: |
          # if running as non-root, add sudo
          apt update
          apt install -y apt-utils
      - name: Debug
        run: |
          #!/bin/bash -x
          export
      - name: Kaniko build dev
        uses: aevea/action-kaniko@master
        with:
          registry: harbor.freshbrewed.science
          image: freshbrewedprivate/docusaurus
          username: kaniko
          password: $
          cache: true
          cache_registry: node:lts
          target: development
          tag: "dev$$"
      - name: Kaniko build prod
        uses: aevea/action-kaniko@master
        with:
          registry: harbor.freshbrewed.science
          image: freshbrewedprivate/docusaurus
          username: kaniko
          password: $
          cache: true
          cache_registry: node:lts
          target: production
          tag: "prod$$"
      - name: test
        run: |
          # Install Kubectl
          curl -LO https://dl.k8s.io/release/v1.28.4/bin/linux/amd64/kubectl
          chmod 755 ./kubectl

          # Get Kubeconfig
          echo $ | base64 --decode > /tmp/k8s

          # Show Deployments
          ./kubectl get deployments --kubeconfig=/tmp/k8s

          # Update Image
          ./kubectl set image deployment/docusaurus-deployment docusaurus-container=harbor.freshbrewed.science/freshbrewedprivate/docusaurus:dev$$
          

which we can see

/content/images/2023/11/docusaurus2-15.png

And I can see them

/content/images/2023/11/docusaurus2-16.png

What we can see is that after building, it deployed

$ kubectl get deployments docusaurus-deployment -o yaml | grep -i image:
      - image: harbor.freshbrewed.science/freshbrewedprivate/docusaurus:devmain25

Let’s do an end-to-end demo:

Summary

We made several modifications to Docusaurus, including adjustments to its color scheme, links, and graphics. We then proceeded to modify the Docker containers and build them using a Gitea workflow before pushing them to Harbor CR. Finally, we demonstrated an end-to-end demo of the development and production container builds using Kaniko, followed by an automated deployment.

Docusaurus Kubernetes Docker

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes